feat(webrtc): add ICE config helper (Configuration + SettingEngine)

Vendors github.com/pion/webrtc/v4 v4.2.11 and its transitive
dependencies (datachannel, dtls/v3, ice/v4, interceptor, logging,
mdns/v2, sctp, sdp/v3, srtp/v3, stun/v3, transport/v4, turn/v4).
This commit is contained in:
Zac Gaetano 2026-04-17 08:46:27 -04:00
parent 1fdc29ace1
commit 917c353e03
698 changed files with 109266 additions and 0 deletions

47
core/webrtc/ice.go Normal file
View file

@ -0,0 +1,47 @@
package webrtc
import (
"github.com/pion/webrtc/v4"
)
// BuildICEConfig translates a Config into the two Pion config pieces every
// PeerConnection needs: a webrtc.Configuration (with ICE servers) and a
// SettingEngine (with NAT1To1 and port range tuning).
//
// The returned *SettingEngine may be nil if no engine-level tuning is
// required (i.e. PublicIP unset and UDPPortRange at defaults). Callers
// should only pass it to webrtc.NewAPI when non-nil.
func BuildICEConfig(c Config) (webrtc.Configuration, *webrtc.SettingEngine, error) {
if err := c.Validate(); err != nil {
return webrtc.Configuration{}, nil, err
}
rtcConfig := webrtc.Configuration{
ICEServers: make([]webrtc.ICEServer, 0, len(c.ICEServers)),
}
for _, uri := range c.ICEServers {
rtcConfig.ICEServers = append(rtcConfig.ICEServers, webrtc.ICEServer{
URLs: []string{uri},
})
}
var se *webrtc.SettingEngine
if c.PublicIP != "" || c.UDPPortRange.Low > 0 {
engine := webrtc.SettingEngine{}
if c.PublicIP != "" {
engine.SetNAT1To1IPs([]string{c.PublicIP}, webrtc.ICECandidateTypeHost)
}
// Constrain the ephemeral UDP range Pion allocates for ICE candidates.
// Note: this is a separate concern from our FFmpeg→Source UDP ports;
// Pion uses its own port pool for the WebRTC media path.
if c.UDPPortRange.Low > 0 && c.UDPPortRange.High >= c.UDPPortRange.Low {
if err := engine.SetEphemeralUDPPortRange(
uint16(c.UDPPortRange.Low), uint16(c.UDPPortRange.High)); err != nil {
return webrtc.Configuration{}, nil, err
}
}
se = &engine
}
return rtcConfig, se, nil
}

50
core/webrtc/ice_test.go Normal file
View file

@ -0,0 +1,50 @@
package webrtc
import (
"testing"
"github.com/pion/webrtc/v4"
)
func TestBuildICEConfig_Defaults(t *testing.T) {
c := DefaultConfig()
rtcConfig, _, err := BuildICEConfig(c)
if err != nil {
t.Fatalf("BuildICEConfig: %v", err)
}
if len(rtcConfig.ICEServers) == 0 {
t.Error("ICEServers should not be empty")
}
// First default is Cloudflare STUN.
if rtcConfig.ICEServers[0].URLs[0] != "stun:stun.cloudflare.com:3478" {
t.Errorf("first ICE server = %q, want stun:stun.cloudflare.com:3478",
rtcConfig.ICEServers[0].URLs[0])
}
}
func TestBuildICEConfig_PublicIP(t *testing.T) {
c := DefaultConfig()
c.PublicIP = "203.0.113.10"
_, se, err := BuildICEConfig(c)
if err != nil {
t.Fatalf("BuildICEConfig: %v", err)
}
if se == nil {
t.Fatal("SettingEngine should not be nil when PublicIP is set")
}
// We can't introspect NAT1To1IPs directly from Pion's public API; the
// smoke test is that building an API from this engine works.
api := webrtc.NewAPI(webrtc.WithSettingEngine(*se))
if api == nil {
t.Fatal("NewAPI returned nil")
}
}
func TestBuildICEConfig_InvalidConfig(t *testing.T) {
c := DefaultConfig()
c.WHEPListen = ""
_, _, err := BuildICEConfig(c)
if err == nil {
t.Error("BuildICEConfig should reject invalid config")
}
}

15
go.mod
View file

@ -21,6 +21,7 @@ require (
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.70 github.com/minio/minio-go/v7 v7.0.70
github.com/pion/rtp v1.10.1 github.com/pion/rtp v1.10.1
github.com/pion/webrtc/v4 v4.2.11
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/puzpuzpuz/xsync/v3 v3.1.0 github.com/puzpuzpuz/xsync/v3 v3.1.0
@ -72,7 +73,20 @@ require (
github.com/miekg/dns v1.1.59 // indirect github.com/miekg/dns v1.1.59 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pion/datachannel v1.6.0 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect
github.com/pion/ice/v4 v4.2.2 // indirect
github.com/pion/interceptor v0.1.44 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.1.0 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.16 // indirect
github.com/pion/sctp v1.9.4 // indirect
github.com/pion/sdp/v3 v3.0.18 // indirect
github.com/pion/srtp/v3 v3.0.10 // indirect
github.com/pion/stun/v3 v3.1.1 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pion/turn/v4 v4.1.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
@ -88,6 +102,7 @@ require (
github.com/urfave/cli/v2 v2.27.2 // indirect github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect

32
go.sum
View file

@ -134,10 +134,40 @@ github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJn
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo= github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
github.com/pion/ice/v4 v4.2.2 h1:dQJzzcgTFHDYyV3BoCfjPeX+JEtr58BWPi4PGyo6Vjg=
github.com/pion/ice/v4 v4.2.2/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c=
github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I=
github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA= github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=
github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.9.4 h1:cMxEu0F5tbP4qH07bKf1Zjf4rUih9LIo0qQt424e258=
github.com/pion/sctp v1.9.4/go.mod h1:N20Dq6LY+JvJDAh9VVh1JELngb2rQ8dPgds5yBWiPgw=
github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=
github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
github.com/pion/webrtc/v4 v4.2.11 h1:QUX1QZKlNIn4O7U5JxLPGP0sV5RTncZkzu9SPR3jVNU=
github.com/pion/webrtc/v4 v4.2.11/go.mod h1:s/rAiyy77GyRFrZMx+Ls6aua26dIBPudH8/ZHYbIRWY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -204,6 +234,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.12 h1:COMhVVnql6RoaF7+aTBWiTADdpLGyZWU3K/NwW0ph98= github.com/vektah/gqlparser/v2 v2.5.12 h1:COMhVVnql6RoaF7+aTBWiTADdpLGyZWU3K/NwW0ph98=
github.com/vektah/gqlparser/v2 v2.5.12/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w= github.com/vektah/gqlparser/v2 v2.5.12/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=

28
vendor/github.com/pion/datachannel/.gitignore generated vendored Normal file
View file

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### JetBrains IDE ###
#####################
.idea/
### Emacs Temporary Files ###
#############################
*~
### Folders ###
###############
bin/
vendor/
node_modules/
### Files ###
#############
*.ivf
*.ogg
tags
cover.out
*.sw[poe]
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem
wasm_exec.js

147
vendor/github.com/pion/datachannel/.golangci.yml generated vendored Normal file
View file

@ -0,0 +1,147 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
version: "2"
linters:
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- containedctx # containedctx is a linter that detects struct contained context.Context field
- contextcheck # check the function whether use a non-inherited context
- cyclop # checks function and package cyclomatic complexity
- decorder # check declaration order and count of types, constants, variables and functions
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
- durationcheck # check for two durations multiplied together
- err113 # Golang linter to check the errors handling expressions
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
- exhaustive # check exhaustiveness of enum switch statements
- forbidigo # Forbids identifiers
- forcetypeassert # finds forced type assertions
- gochecknoglobals # Checks that no globals are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- godot # Check if comments end in a period
- godox # Tool for detection of FIXME, TODO and other comment keywords
- goheader # Checks is file header matches to pattern
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
- gosec # Inspects source code for security problems
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- grouper # An analyzer to analyze expression groups.
- importas # Enforces consistent import aliases
- ineffassign # Detects when assignments to existing variables are not used
- lll # Reports long lines
- maintidx # maintidx measures the maintainability index of each function.
- makezero # Finds slice declarations with non-zero initial length
- misspell # Finds commonly misspelled English words in comments
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
- noctx # noctx finds sending http request without context.Context
- predeclared # find code that shadows one of Go's predeclared identifiers
- revive # golint replacement, finds style mistakes
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- tagliatelle # Checks the struct tags.
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
- unconvert # Remove unnecessary type conversions
- unparam # Reports unused function parameters
- unused # Checks Go code for unused constants, variables, functions and types
- varnamelen # checks that the length of a variable's name matches its scope
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- funlen # Tool for detection of long functions
- gochecknoinits # Checks that no init functions are present in Go code
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
- interfacebloat # A linter that checks length of interface.
- ireturn # Accept Interfaces, Return Concrete Types
- mnd # An analyzer to detect magic numbers
- nolintlint # Reports ill-formed or insufficient nolint directives
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
- prealloc # Finds slice declarations that could potentially be preallocated
- promlinter # Check Prometheus metrics naming via promlint
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
- testpackage # linter that makes you use a separate _test package
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
- wrapcheck # Checks that errors returned from external packages are wrapped
- wsl # Whitespace Linter - Forces you to use empty lines!
settings:
staticcheck:
checks:
- all
- -QF1008 # "could remove embedded field", to keep it explicit!
- -QF1003 # "could use tagged switch on enum", Cases conflicts with exhaustive!
exhaustive:
default-signifies-exhaustive: true
forbidigo:
forbid:
- pattern: ^fmt.Print(f|ln)?$
- pattern: ^log.(Panic|Fatal|Print)(f|ln)?$
- pattern: ^os.Exit$
- pattern: ^panic$
- pattern: ^print(ln)?$
- pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$
pkg: ^testing$
msg: use testify/assert instead
analyze-types: true
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
govet:
enable:
- shadow
revive:
rules:
# Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility
- name: use-any
severity: warning
disabled: false
misspell:
locale: US
varnamelen:
max-distance: 12
min-name-length: 2
ignore-type-assert-ok: true
ignore-map-index-ok: true
ignore-chan-recv-ok: true
ignore-decls:
- i int
- n int
- w io.Writer
- r io.Reader
- b []byte
exclusions:
generated: lax
rules:
- linters:
- forbidigo
- gocognit
path: (examples|main\.go)
- linters:
- gocognit
path: _test\.go
- linters:
- forbidigo
path: cmd
formatters:
enable:
- gci # Gci control golang package import order and make it always deterministic.
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
exclusions:
generated: lax

5
vendor/github.com/pion/datachannel/.goreleaser.yml generated vendored Normal file
View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

9
vendor/github.com/pion/datachannel/LICENSE generated vendored Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 The Pion community <https://pion.ly>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

34
vendor/github.com/pion/datachannel/README.md generated vendored Normal file
View file

@ -0,0 +1,34 @@
<h1 align="center">
<br>
Pion Data Channels
<br>
</h1>
<h4 align="center">A Go implementation of WebRTC Data Channels</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-datachannel-gray.svg?longCache=true&colorB=brightgreen" alt="Pion Data Channels"></a>
<a href="https://discord.gg/PngbdqpFbt"><img src="https://img.shields.io/badge/join-us%20on%20discord-gray.svg?longCache=true&logo=discord&colorB=brightblue" alt="join us on Discord"></a> <a href="https://bsky.app/profile/pion.ly"><img src="https://img.shields.io/badge/follow-us%20on%20bluesky-gray.svg?longCache=true&logo=bluesky&colorB=brightblue" alt="Follow us on Bluesky"></a>
<br>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/datachannel/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/datachannel"><img src="https://pkg.go.dev/badge/github.com/pion/datachannel.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/datachannel"><img src="https://codecov.io/gh/pion/datachannel/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/datachannel"><img src="https://goreportcard.com/badge/github.com/pion/datachannel" alt="Go Report Card"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>
### Roadmap
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
### Community
Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt).
Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
We are always looking to support **your projects**. Please reach out if you have something to build!
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
### Contributing
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
### License
MIT License - see [LICENSE](LICENSE) for full text

22
vendor/github.com/pion/datachannel/codecov.yml generated vendored Normal file
View file

@ -0,0 +1,22 @@
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
coverage:
status:
project:
default:
# Allow decreasing 2% of total coverage to avoid noise.
threshold: 2%
patch:
default:
target: 70%
only_pulls: true
ignore:
- "examples/*"
- "examples/**/*"

445
vendor/github.com/pion/datachannel/datachannel.go generated vendored Normal file
View file

@ -0,0 +1,445 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package datachannel implements WebRTC Data Channels
package datachannel
import (
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"github.com/pion/logging"
"github.com/pion/sctp"
)
const receiveMTU = 8192
// Reader is an extended io.Reader
// that also returns if the message is text.
type Reader interface {
ReadDataChannel([]byte) (int, bool, error)
}
// ReadDeadliner extends an io.Reader to expose setting a read deadline.
type ReadDeadliner interface {
SetReadDeadline(time.Time) error
}
// Writer is an extended io.Writer
// that also allows indicating if a message is text.
type Writer interface {
WriteDataChannel([]byte, bool) (int, error)
}
// WriteDeadliner extends an io.Writer to expose setting a write deadline.
type WriteDeadliner interface {
SetWriteDeadline(time.Time) error
}
// ReadWriteCloser is an extended io.ReadWriteCloser
// that also implements our Reader and Writer.
type ReadWriteCloser interface {
io.Reader
io.Writer
Reader
Writer
io.Closer
}
// ReadWriteCloserDeadliner is an extended ReadWriteCloser
// that also implements r/w deadline.
type ReadWriteCloserDeadliner interface {
ReadWriteCloser
ReadDeadliner
WriteDeadliner
}
// DataChannel represents a data channel.
type DataChannel struct {
Config
// stats
messagesSent uint32
messagesReceived uint32
bytesSent uint64
bytesReceived uint64
mu sync.Mutex
onOpenCompleteHandler func()
openCompleteHandlerOnce sync.Once
stream *sctp.Stream
log logging.LeveledLogger
}
// Config is used to configure the data channel.
type Config struct {
ChannelType ChannelType
Negotiated bool
Priority uint16
ReliabilityParameter uint32
Label string
Protocol string
LoggerFactory logging.LoggerFactory
}
func newDataChannel(stream *sctp.Stream, config *Config) *DataChannel {
return &DataChannel{
Config: *config,
stream: stream,
log: config.LoggerFactory.NewLogger("datachannel"),
}
}
// Dial opens a data channels over SCTP.
func Dial(a *sctp.Association, id uint16, config *Config) (*DataChannel, error) {
stream, err := a.OpenStream(id, sctp.PayloadTypeWebRTCBinary)
if err != nil {
return nil, err
}
dc, err := Client(stream, config)
if err != nil {
return nil, err
}
isReliable := dc.ChannelType == ChannelTypeReliable || dc.ChannelType == ChannelTypeReliableUnordered
if isReliable && dc.ReliabilityParameter != 0 {
dc.log.Warnf("DataChannel opened with channel type %s, but has a non-zero reliability parameter: %d (expected 0)",
dc.ChannelType,
dc.ReliabilityParameter)
}
return dc, nil
}
// Client opens a data channel over an SCTP stream.
func Client(stream *sctp.Stream, config *Config) (*DataChannel, error) {
msg := &channelOpen{
ChannelType: config.ChannelType,
Priority: config.Priority,
ReliabilityParameter: config.ReliabilityParameter,
Label: []byte(config.Label),
Protocol: []byte(config.Protocol),
}
if !config.Negotiated {
rawMsg, err := msg.Marshal()
if err != nil {
return nil, fmt.Errorf("failed to marshal ChannelOpen %w", err)
}
if _, err = stream.WriteSCTP(rawMsg, sctp.PayloadTypeWebRTCDCEP); err != nil {
return nil, fmt.Errorf("failed to send ChannelOpen %w", err)
}
}
return newDataChannel(stream, config), nil
}
// Accept is used to accept incoming data channels over SCTP.
func Accept(a *sctp.Association, config *Config, existingChannels ...*DataChannel) (*DataChannel, error) {
stream, err := a.AcceptStream()
if err != nil {
return nil, err
}
for _, ch := range existingChannels {
if ch.StreamIdentifier() == stream.StreamIdentifier() {
ch.stream.SetDefaultPayloadType(sctp.PayloadTypeWebRTCBinary)
return ch, nil
}
}
stream.SetDefaultPayloadType(sctp.PayloadTypeWebRTCBinary)
dc, err := Server(stream, config)
if err != nil {
return nil, err
}
return dc, nil
}
// Server accepts a data channel over an SCTP stream.
func Server(stream *sctp.Stream, config *Config) (*DataChannel, error) {
buffer := make([]byte, receiveMTU)
n, ppi, err := stream.ReadSCTP(buffer)
if err != nil {
return nil, err
}
if ppi != sctp.PayloadTypeWebRTCDCEP {
return nil, fmt.Errorf("%w %s", ErrInvalidPayloadProtocolIdentifier, ppi)
}
openMsg, err := parseExpectDataChannelOpen(buffer[:n])
if err != nil {
return nil, fmt.Errorf("failed to parse DataChannelOpen packet %w", err)
}
config.ChannelType = openMsg.ChannelType
config.Priority = openMsg.Priority
config.ReliabilityParameter = openMsg.ReliabilityParameter
config.Label = string(openMsg.Label)
config.Protocol = string(openMsg.Protocol)
dataChannel := newDataChannel(stream, config)
err = dataChannel.writeDataChannelAck()
if err != nil {
return nil, err
}
err = dataChannel.commitReliabilityParams()
if err != nil {
return nil, err
}
return dataChannel, nil
}
// Read reads a packet of len(pkt) bytes as binary data.
func (c *DataChannel) Read(pkt []byte) (int, error) {
n, _, err := c.ReadDataChannel(pkt)
return n, err
}
// ReadDataChannel reads a packet of len(pkt) bytes.
func (c *DataChannel) ReadDataChannel(pkt []byte) (int, bool, error) {
for {
n, ppi, err := c.stream.ReadSCTP(pkt)
if errors.Is(err, io.EOF) {
// When the peer sees that an incoming stream was
// reset, it also resets its corresponding outgoing stream.
if closeErr := c.stream.Close(); closeErr != nil {
return 0, false, closeErr
}
}
if err != nil {
return 0, false, err
}
if ppi == sctp.PayloadTypeWebRTCDCEP {
if err = c.handleDCEP(pkt[:n]); err != nil {
c.log.Errorf("Failed to handle DCEP: %s", err.Error())
}
continue
} else if ppi == sctp.PayloadTypeWebRTCBinaryEmpty || ppi == sctp.PayloadTypeWebRTCStringEmpty {
n = 0
}
atomic.AddUint32(&c.messagesReceived, 1)
atomic.AddUint64(&c.bytesReceived, uint64(n)) //nolint:gosec //G115
isString := ppi == sctp.PayloadTypeWebRTCString || ppi == sctp.PayloadTypeWebRTCStringEmpty
return n, isString, err
}
}
// SetReadDeadline sets a deadline for reads to return.
func (c *DataChannel) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
// SetWriteDeadline sets a deadline for writes to return,
// only available if the BlockWrite is enabled for sctp.
func (c *DataChannel) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}
// MessagesSent returns the number of messages sent.
func (c *DataChannel) MessagesSent() uint32 {
return atomic.LoadUint32(&c.messagesSent)
}
// MessagesReceived returns the number of messages received.
func (c *DataChannel) MessagesReceived() uint32 {
return atomic.LoadUint32(&c.messagesReceived)
}
// OnOpen sets an event handler which is invoked when
// a DATA_CHANNEL_ACK message is received.
// The handler is called only on thefor the channel opened
// https://datatracker.ietf.org/doc/html/draft-ietf-rtcweb-data-protocol-09#section-5.2
func (c *DataChannel) OnOpen(f func()) {
c.mu.Lock()
c.openCompleteHandlerOnce = sync.Once{}
c.onOpenCompleteHandler = f
c.mu.Unlock()
}
func (c *DataChannel) onOpenComplete() {
c.mu.Lock()
hdlr := c.onOpenCompleteHandler
c.mu.Unlock()
if hdlr != nil {
go c.openCompleteHandlerOnce.Do(func() {
hdlr()
})
}
}
// BytesSent returns the number of bytes sent.
func (c *DataChannel) BytesSent() uint64 {
return atomic.LoadUint64(&c.bytesSent)
}
// BytesReceived returns the number of bytes received.
func (c *DataChannel) BytesReceived() uint64 {
return atomic.LoadUint64(&c.bytesReceived)
}
// StreamIdentifier returns the Stream identifier associated to the stream.
func (c *DataChannel) StreamIdentifier() uint16 {
return c.stream.StreamIdentifier()
}
func (c *DataChannel) handleDCEP(data []byte) error {
msg, err := parse(data)
if err != nil {
return fmt.Errorf("failed to parse DataChannel packet %w", err)
}
switch msg := msg.(type) {
case *channelAck:
if err := c.commitReliabilityParams(); err != nil {
return err
}
c.onOpenComplete()
default:
return fmt.Errorf("%w, wanted ACK got %v", ErrUnexpectedDataChannelType, msg)
}
return nil
}
// Write writes len(pkt) bytes from pkt as binary data.
func (c *DataChannel) Write(pkt []byte) (n int, err error) {
return c.WriteDataChannel(pkt, false)
}
// WriteDataChannel writes len(pkt) bytes from pkt.
func (c *DataChannel) WriteDataChannel(pkt []byte, isString bool) (n int, err error) {
// https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-12#section-6.6
// SCTP does not support the sending of empty user messages. Therefore,
// if an empty message has to be sent, the appropriate PPID (WebRTC
// String Empty or WebRTC Binary Empty) is used and the SCTP user
// message of one zero byte is sent. When receiving an SCTP user
// message with one of these PPIDs, the receiver MUST ignore the SCTP
// user message and process it as an empty message.
var ppi sctp.PayloadProtocolIdentifier
switch {
case !isString && len(pkt) > 0:
ppi = sctp.PayloadTypeWebRTCBinary
case !isString && len(pkt) == 0:
ppi = sctp.PayloadTypeWebRTCBinaryEmpty
case isString && len(pkt) > 0:
ppi = sctp.PayloadTypeWebRTCString
case isString && len(pkt) == 0:
ppi = sctp.PayloadTypeWebRTCStringEmpty
}
atomic.AddUint32(&c.messagesSent, 1)
atomic.AddUint64(&c.bytesSent, uint64(len(pkt)))
if len(pkt) == 0 {
_, err := c.stream.WriteSCTP([]byte{0}, ppi)
return 0, err
}
return c.stream.WriteSCTP(pkt, ppi)
}
func (c *DataChannel) writeDataChannelAck() error {
ack := channelAck{}
ackMsg, err := ack.Marshal()
if err != nil {
return fmt.Errorf("failed to marshal ChannelOpen ACK: %w", err)
}
if _, err = c.stream.WriteSCTP(ackMsg, sctp.PayloadTypeWebRTCDCEP); err != nil {
return fmt.Errorf("failed to send ChannelOpen ACK: %w", err)
}
return err
}
// Close closes the DataChannel and the underlying SCTP stream.
func (c *DataChannel) Close() error {
// https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.7
// Closing of a data channel MUST be signaled by resetting the
// corresponding outgoing streams [RFC6525]. This means that if one
// side decides to close the data channel, it resets the corresponding
// outgoing stream. When the peer sees that an incoming stream was
// reset, it also resets its corresponding outgoing stream. Once this
// is completed, the data channel is closed. Resetting a stream sets
// the Stream Sequence Numbers (SSNs) of the stream back to 'zero' with
// a corresponding notification to the application layer that the reset
// has been performed. Streams are available for reuse after a reset
// has been performed.
return c.stream.Close()
}
// BufferedAmount returns the number of bytes of data currently queued to be
// sent over this stream.
func (c *DataChannel) BufferedAmount() uint64 {
return c.stream.BufferedAmount()
}
// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing
// data that is considered "low." Defaults to 0.
func (c *DataChannel) BufferedAmountLowThreshold() uint64 {
return c.stream.BufferedAmountLowThreshold()
}
// SetBufferedAmountLowThreshold is used to update the threshold.
// See BufferedAmountLowThreshold().
func (c *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
c.stream.SetBufferedAmountLowThreshold(th)
}
// OnBufferedAmountLow sets the callback handler which would be called when the
// number of bytes of outgoing data buffered is lower than the threshold.
func (c *DataChannel) OnBufferedAmountLow(f func()) {
c.stream.OnBufferedAmountLow(f)
}
func (c *DataChannel) commitReliabilityParams() error {
switch c.Config.ChannelType {
case ChannelTypeReliable:
c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeReliable, c.Config.ReliabilityParameter) // RFC 8832 sec 5.1
if c.Config.ReliabilityParameter != 0 {
c.log.Warnf("Channel type is Reliable but has a non-zero reliability parameter: %d (expected 0)",
c.Config.ReliabilityParameter)
}
case ChannelTypeReliableUnordered:
c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeReliable, c.Config.ReliabilityParameter) // RFC 8832 sec 5.1
if c.Config.ReliabilityParameter != 0 {
c.log.Warnf("Channel type is ReliableUnordered but has a non-zero reliability parameter: %d (expected 0)",
c.Config.ReliabilityParameter)
}
case ChannelTypePartialReliableRexmit:
c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeRexmit, c.Config.ReliabilityParameter)
case ChannelTypePartialReliableRexmitUnordered:
c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeRexmit, c.Config.ReliabilityParameter)
case ChannelTypePartialReliableTimed:
c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeTimed, c.Config.ReliabilityParameter)
case ChannelTypePartialReliableTimedUnordered:
c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeTimed, c.Config.ReliabilityParameter)
default:
return fmt.Errorf("%w %v", ErrInvalidChannelType, c.Config.ChannelType)
}
return nil
}

29
vendor/github.com/pion/datachannel/errors.go generated vendored Normal file
View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package datachannel
import "errors"
var (
// ErrDataChannelMessageTooShort means that the data isn't long enough to be a valid DataChannel message.
ErrDataChannelMessageTooShort = errors.New("DataChannel message is not long enough to determine type")
// ErrInvalidPayloadProtocolIdentifier means that we got a DataChannel messages with a Payload Protocol Identifier
// we don't know how to handle.
ErrInvalidPayloadProtocolIdentifier = errors.New(
"DataChannel message Payload Protocol Identifier is value we can't handle",
)
// ErrInvalidChannelType means that the remote requested a channel type that we don't support.
ErrInvalidChannelType = errors.New("invalid Channel Type")
// ErrInvalidMessageType is returned when a DataChannel Message has a type we don't support.
ErrInvalidMessageType = errors.New("invalid Message Type")
// ErrExpectedAndActualLengthMismatch is when the declared length and actual length don't match.
ErrExpectedAndActualLengthMismatch = errors.New("expected and actual length do not match")
// ErrUnexpectedDataChannelType is when a message type does not match the expected type.
ErrUnexpectedDataChannelType = errors.New("expected and actual message type does not match")
)

92
vendor/github.com/pion/datachannel/message.go generated vendored Normal file
View file

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package datachannel
import (
"fmt"
)
// message is a parsed DataChannel message.
type message interface {
Marshal() ([]byte, error)
Unmarshal([]byte) error
String() string
}
// messageType is the first byte in a DataChannel message that specifies type.
type messageType byte
// DataChannel Message Types.
const (
dataChannelAck messageType = 0x02
dataChannelOpen messageType = 0x03
)
func (t messageType) String() string {
switch t {
case dataChannelAck:
return "DataChannelAck"
case dataChannelOpen:
return "DataChannelOpen"
default:
return fmt.Sprintf("Unknown MessageType: %d", t)
}
}
// parse accepts raw input and returns a DataChannel message.
func parse(raw []byte) (message, error) {
if len(raw) == 0 {
return nil, ErrDataChannelMessageTooShort
}
var msg message
switch messageType(raw[0]) {
case dataChannelOpen:
msg = &channelOpen{}
case dataChannelAck:
msg = &channelAck{}
default:
return nil, fmt.Errorf("%w %v", ErrInvalidMessageType, messageType(raw[0]))
}
if err := msg.Unmarshal(raw); err != nil {
return nil, err
}
return msg, nil
}
// parseExpectDataChannelOpen parses a DataChannelOpen message
// or throws an error.
func parseExpectDataChannelOpen(raw []byte) (*channelOpen, error) {
if len(raw) == 0 {
return nil, ErrDataChannelMessageTooShort
}
if actualTyp := messageType(raw[0]); actualTyp != dataChannelOpen {
return nil, fmt.Errorf("%w expected(%s) actual(%s)", ErrUnexpectedDataChannelType, actualTyp, dataChannelOpen)
}
msg := &channelOpen{}
if err := msg.Unmarshal(raw); err != nil {
return nil, err
}
return msg, nil
}
// TryMarshalUnmarshal attempts to marshal and unmarshal a message. Added for fuzzing.
func TryMarshalUnmarshal(msg []byte) int {
message, err := parse(msg)
if err != nil {
return 0
}
_, err = message.Marshal()
if err != nil {
return 0
}
return 1
}

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package datachannel
// channelAck is used to ACK a DataChannel open.
type channelAck struct{}
const (
channelOpenAckLength = 4
)
// Marshal returns raw bytes for the given message.
func (c *channelAck) Marshal() ([]byte, error) {
raw := make([]byte, channelOpenAckLength)
raw[0] = uint8(dataChannelAck)
return raw, nil
}
// Unmarshal populates the struct with the given raw data.
func (c *channelAck) Unmarshal(_ []byte) error {
// Message type already checked in Parse and there is no further data
return nil
}
func (c channelAck) String() string {
return "ACK"
}

View file

@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package datachannel
import (
"encoding/binary"
"fmt"
)
/*
channelOpen represents a DATA_CHANNEL_OPEN Message
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type | Channel Type | Priority |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reliability Parameter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Label Length | Protocol Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Label |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Protocol |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
.
*/
type channelOpen struct {
ChannelType ChannelType
Priority uint16
ReliabilityParameter uint32
Label []byte
Protocol []byte
}
const (
channelOpenHeaderLength = 12
)
// ChannelType determines the reliability of the WebRTC DataChannel.
type ChannelType byte
// ChannelType enums.
const (
// ChannelTypeReliable determines the Data Channel provides a
// reliable in-order bi-directional communication.
ChannelTypeReliable ChannelType = 0x00
// ChannelTypeReliableUnordered determines the Data Channel
// provides a reliable unordered bi-directional communication.
ChannelTypeReliableUnordered ChannelType = 0x80
// ChannelTypePartialReliableRexmit determines the Data Channel
// provides a partially-reliable in-order bi-directional communication.
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
ChannelTypePartialReliableRexmit ChannelType = 0x01
// ChannelTypePartialReliableRexmitUnordered determines
// the Data Channel provides a partial reliable unordered bi-directional communication.
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
ChannelTypePartialReliableRexmitUnordered ChannelType = 0x81
// ChannelTypePartialReliableTimed determines the Data Channel
// provides a partial reliable in-order bi-directional communication.
// User messages might not be transmitted or retransmitted after
// a specified life-time given in milli- seconds in the Reliability Parameter.
// This life-time starts when providing the user message to the protocol stack.
ChannelTypePartialReliableTimed ChannelType = 0x02
// The Data Channel provides a partial reliable unordered bi-directional
// communication. User messages might not be transmitted or retransmitted
// after a specified life-time given in milli- seconds in the Reliability Parameter.
// This life-time starts when providing the user message to the protocol stack.
ChannelTypePartialReliableTimedUnordered ChannelType = 0x82
)
func (c ChannelType) String() string {
switch c {
case ChannelTypeReliable:
return "ReliableOrdered"
case ChannelTypeReliableUnordered:
return "ReliableUnordered"
case ChannelTypePartialReliableRexmit:
return "PartialReliableRexmit"
case ChannelTypePartialReliableRexmitUnordered:
return "PartialReliableRexmitUnordered"
case ChannelTypePartialReliableTimed:
return "PartialReliableTimed"
case ChannelTypePartialReliableTimedUnordered:
return "PartialReliableTimedUnordered"
}
return "Unknown"
}
// ChannelPriority enums.
const (
ChannelPriorityBelowNormal uint16 = 128
ChannelPriorityNormal uint16 = 256
ChannelPriorityHigh uint16 = 512
ChannelPriorityExtraHigh uint16 = 1024
)
// Marshal returns raw bytes for the given message.
func (c *channelOpen) Marshal() ([]byte, error) {
labelLength := len(c.Label)
protocolLength := len(c.Protocol)
totalLen := channelOpenHeaderLength + labelLength + protocolLength
raw := make([]byte, totalLen)
raw[0] = uint8(dataChannelOpen)
raw[1] = byte(c.ChannelType)
binary.BigEndian.PutUint16(raw[2:], c.Priority)
binary.BigEndian.PutUint32(raw[4:], c.ReliabilityParameter)
binary.BigEndian.PutUint16(raw[8:], uint16(labelLength)) //nolint:gosec //G115
binary.BigEndian.PutUint16(raw[10:], uint16(protocolLength)) //nolint:gosec //G115
endLabel := channelOpenHeaderLength + labelLength
copy(raw[channelOpenHeaderLength:endLabel], c.Label)
copy(raw[endLabel:endLabel+protocolLength], c.Protocol)
return raw, nil
}
// Unmarshal populates the struct with the given raw data.
func (c *channelOpen) Unmarshal(raw []byte) error {
if len(raw) < channelOpenHeaderLength {
return fmt.Errorf("%w expected(%d) actual(%d)", ErrExpectedAndActualLengthMismatch, channelOpenHeaderLength, len(raw))
}
c.ChannelType = ChannelType(raw[1])
c.Priority = binary.BigEndian.Uint16(raw[2:])
c.ReliabilityParameter = binary.BigEndian.Uint32(raw[4:])
labelLength := binary.BigEndian.Uint16(raw[8:])
protocolLength := binary.BigEndian.Uint16(raw[10:])
if expectedLen := channelOpenHeaderLength + int(labelLength) + int(protocolLength); len(raw) != expectedLen {
return fmt.Errorf("%w expected(%d) actual(%d)", ErrExpectedAndActualLengthMismatch, expectedLen, len(raw))
}
c.Label = raw[channelOpenHeaderLength : channelOpenHeaderLength+labelLength]
c.Protocol = raw[channelOpenHeaderLength+labelLength : channelOpenHeaderLength+labelLength+protocolLength]
return nil
}
func (c channelOpen) String() string {
return fmt.Sprintf(
"Open ChannelType(%s) Priority(%v) ReliabilityParameter(%d) Label(%s) Protocol(%s)",
c.ChannelType, c.Priority, c.ReliabilityParameter, string(c.Label), string(c.Protocol),
)
}

6
vendor/github.com/pion/datachannel/renovate.json generated vendored Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>pion/renovate-config"
]
}

23
vendor/github.com/pion/dtls/v3/.editorconfig generated vendored Normal file
View file

@ -0,0 +1,23 @@
# http://editorconfig.org/
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.go]
indent_style = tab
indent_size = 4
[{*.yml,*.yaml}]
indent_style = space
indent_size = 2
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab

28
vendor/github.com/pion/dtls/v3/.gitignore generated vendored Normal file
View file

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### JetBrains IDE ###
#####################
.idea/
### Emacs Temporary Files ###
#############################
*~
### Folders ###
###############
bin/
vendor/
node_modules/
### Files ###
#############
*.ivf
*.ogg
tags
cover.out
*.sw[poe]
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem
wasm_exec.js

147
vendor/github.com/pion/dtls/v3/.golangci.yml generated vendored Normal file
View file

@ -0,0 +1,147 @@
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
version: "2"
linters:
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- containedctx # containedctx is a linter that detects struct contained context.Context field
- contextcheck # check the function whether use a non-inherited context
- cyclop # checks function and package cyclomatic complexity
- decorder # check declaration order and count of types, constants, variables and functions
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
- durationcheck # check for two durations multiplied together
- err113 # Golang linter to check the errors handling expressions
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
- exhaustive # check exhaustiveness of enum switch statements
- forbidigo # Forbids identifiers
- forcetypeassert # finds forced type assertions
- gochecknoglobals # Checks that no globals are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- godot # Check if comments end in a period
- godox # Tool for detection of FIXME, TODO and other comment keywords
- goheader # Checks is file header matches to pattern
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
- gosec # Inspects source code for security problems
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- grouper # An analyzer to analyze expression groups.
- importas # Enforces consistent import aliases
- ineffassign # Detects when assignments to existing variables are not used
- lll # Reports long lines
- maintidx # maintidx measures the maintainability index of each function.
- makezero # Finds slice declarations with non-zero initial length
- misspell # Finds commonly misspelled English words in comments
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
- noctx # noctx finds sending http request without context.Context
- predeclared # find code that shadows one of Go's predeclared identifiers
- revive # golint replacement, finds style mistakes
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- tagliatelle # Checks the struct tags.
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
- unconvert # Remove unnecessary type conversions
- unparam # Reports unused function parameters
- unused # Checks Go code for unused constants, variables, functions and types
- varnamelen # checks that the length of a variable's name matches its scope
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- funlen # Tool for detection of long functions
- gochecknoinits # Checks that no init functions are present in Go code
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
- interfacebloat # A linter that checks length of interface.
- ireturn # Accept Interfaces, Return Concrete Types
- mnd # An analyzer to detect magic numbers
- nolintlint # Reports ill-formed or insufficient nolint directives
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
- prealloc # Finds slice declarations that could potentially be preallocated
- promlinter # Check Prometheus metrics naming via promlint
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
- testpackage # linter that makes you use a separate _test package
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
- wrapcheck # Checks that errors returned from external packages are wrapped
- wsl # Whitespace Linter - Forces you to use empty lines!
settings:
staticcheck:
checks:
- all
- -QF1008 # "could remove embedded field", to keep it explicit!
- -QF1003 # "could use tagged switch on enum", Cases conflicts with exhaustive!
exhaustive:
default-signifies-exhaustive: true
forbidigo:
forbid:
- pattern: ^fmt.Print(f|ln)?$
- pattern: ^log.(Panic|Fatal|Print)(f|ln)?$
- pattern: ^os.Exit$
- pattern: ^panic$
- pattern: ^print(ln)?$
- pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$
pkg: ^testing$
msg: use testify/assert instead
analyze-types: true
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
govet:
enable:
- shadow
revive:
rules:
# Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility
- name: use-any
severity: warning
disabled: false
misspell:
locale: US
varnamelen:
max-distance: 12
min-name-length: 2
ignore-type-assert-ok: true
ignore-map-index-ok: true
ignore-chan-recv-ok: true
ignore-decls:
- i int
- n int
- w io.Writer
- r io.Reader
- b []byte
exclusions:
generated: lax
rules:
- linters:
- forbidigo
- gocognit
path: (examples|main\.go)
- linters:
- gocognit
path: _test\.go
- linters:
- forbidigo
path: cmd
formatters:
enable:
- gci # Gci control golang package import order and make it always deterministic.
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
exclusions:
generated: lax

5
vendor/github.com/pion/dtls/v3/.goreleaser.yml generated vendored Normal file
View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

9
vendor/github.com/pion/dtls/v3/LICENSE generated vendored Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2026 The Pion community <https://pion.ly>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

159
vendor/github.com/pion/dtls/v3/README.md generated vendored Normal file
View file

@ -0,0 +1,159 @@
<h1 align="center">
<br>
Pion DTLS
<br>
</h1>
<h4 align="center">A Go implementation of DTLS</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-dtls-gray.svg?longCache=true&colorB=brightgreen" alt="Pion DTLS"></a>
<a href="https://sourcegraph.com/github.com/pion/dtls"><img src="https://sourcegraph.com/github.com/pion/dtls/-/badge.svg" alt="Sourcegraph Widget"></a>
<a href="https://discord.gg/PngbdqpFbt"><img src="https://img.shields.io/badge/join-us%20on%20discord-gray.svg?longCache=true&logo=discord&colorB=brightblue" alt="join us on Discord"></a> <a href="https://bsky.app/profile/pion.ly"><img src="https://img.shields.io/badge/follow-us%20on%20bluesky-gray.svg?longCache=true&logo=bluesky&colorB=brightblue" alt="Follow us on Bluesky"></a>
<br>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/dtls/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/dtls/v3"><img src="https://pkg.go.dev/badge/github.com/pion/dtls/v3.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/dtls"><img src="https://codecov.io/gh/pion/dtls/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/dtls/v3"><img src="https://goreportcard.com/badge/github.com/pion/dtls/v3" alt="Go Report Card"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>
Native [DTLS 1.2][rfc6347] implementation in the Go programming language.
A long term goal is a professional security review, and maybe an inclusion in stdlib.
### RFCs
#### Implemented
- **RFC 6347**: [Datagram Transport Layer Security Version 1.2][rfc6347]
- **RFC 5705**: [Keying Material Exporters for Transport Layer Security (TLS)][rfc5705]
- **RFC 7627**: [Transport Layer Security (TLS) - Session Hash and Extended Master Secret Extension][rfc7627]
- **RFC 7301**: [Transport Layer Security (TLS) - Application-Layer Protocol Negotiation Extension][rfc7301]
[rfc5289]: https://tools.ietf.org/html/rfc5289
[rfc5487]: https://tools.ietf.org/html/rfc5487
[rfc5489]: https://tools.ietf.org/html/rfc5489
[rfc5705]: https://tools.ietf.org/html/rfc5705
[rfc6347]: https://tools.ietf.org/html/rfc6347
[rfc6655]: https://tools.ietf.org/html/rfc6655
[rfc7301]: https://tools.ietf.org/html/rfc7301
[rfc7627]: https://tools.ietf.org/html/rfc7627
[rfc8422]: https://tools.ietf.org/html/rfc8422
[rfc9147]: https://tools.ietf.org/html/rfc9147
### Goals/Progress
This will only be targeting DTLS 1.2, and the most modern/common cipher suites.
We would love contributions that fall under the 'Planned Features' and any bug fixes!
#### Current features
* DTLS 1.2 Client/Server
* Key Exchange via ECDHE(curve25519, nistp256, nistp384) and PSK
* Packet loss and re-ordering is handled during handshaking
* Key export ([RFC 5705][rfc5705])
* Serialization and Resumption of sessions
* Extended Master Secret extension ([RFC 7627][rfc7627])
* ALPN extension ([RFC 7301][rfc7301])
#### Supported ciphers
##### ECDHE
* TLS_ECDHE_ECDSA_WITH_AES_128_CCM ([RFC 6655][rfc6655])
* TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 ([RFC 6655][rfc6655])
* TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ([RFC 5289][rfc5289])
* TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ([RFC 5289][rfc5289])
* TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ([RFC 5289][rfc5289])
* TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ([RFC 5289][rfc5289])
* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ([RFC 8422][rfc8422])
* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ([RFC 8422][rfc8422])
##### PSK
* TLS_PSK_WITH_AES_128_CCM ([RFC 6655][rfc6655])
* TLS_PSK_WITH_AES_128_CCM_8 ([RFC 6655][rfc6655])
* TLS_PSK_WITH_AES_256_CCM_8 ([RFC 6655][rfc6655])
* TLS_PSK_WITH_AES_128_GCM_SHA256 ([RFC 5487][rfc5487])
* TLS_PSK_WITH_AES_128_CBC_SHA256 ([RFC 5487][rfc5487])
##### ECDHE & PSK
* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 ([RFC 5489][rfc5489])
#### Planned Features
* DTLS 1.3 ([RFC 9147][rfc9147])
* Chacha20Poly1305
#### Excluded Features
* DTLS 1.0
* Renegotiation
* Compression
### Using
This library needs at least Go 1.21, and you should have [Go modules
enabled](https://github.com/golang/go/wiki/Modules).
#### Pion DTLS
For a DTLS 1.2 Server that listens on 127.0.0.1:4444
```sh
go run examples/listen/selfsign/main.go
```
For a DTLS 1.2 Client that connects to 127.0.0.1:4444
```sh
go run examples/dial/selfsign/main.go
```
#### OpenSSL
Pion DTLS can connect to itself and OpenSSL.
```
// Generate a certificate
openssl ecparam -out key.pem -name prime256v1 -genkey
openssl req -new -sha256 -key key.pem -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey key.pem -out cert.pem
// Use with examples/dial/selfsign/main.go
openssl s_server -dtls1_2 -cert cert.pem -key key.pem -accept 4444
// Use with examples/listen/selfsign/main.go
openssl s_client -dtls1_2 -connect 127.0.0.1:4444 -debug -cert cert.pem -key key.pem
```
### Using with PSK
Pion DTLS also comes with examples that do key exchange via PSK
#### Pion DTLS
```sh
go run examples/listen/psk/main.go
```
```sh
go run examples/dial/psk/main.go
```
#### OpenSSL
```
// Use with examples/dial/psk/main.go
openssl s_server -dtls1_2 -accept 4444 -nocert -psk abc123 -cipher PSK-AES128-CCM8
// Use with examples/listen/psk/main.go
openssl s_client -dtls1_2 -connect 127.0.0.1:4444 -psk abc123 -cipher PSK-AES128-CCM8
```
### Community
Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt).
Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
We are always looking to support **your projects**. Please reach out if you have something to build!
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
### Contributing
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
### Funding
<a href="https://nlnet.nl/"><img src="https://nlnet.nl/logo/banner.svg" alt="NLnet foundation logo" width="200"></a>
<a href="https://nlnet.nl/commonsfund/"><img src="https://nlnet.nl/image/logos/NGI0Core_tag.svg" alt="NLnet foundation logo" width="200"></a>
The DTLS 1.3 implementation in this project is funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement No [101135429](https://cordis.europa.eu/project/id/101135429). Additional funding is made available by the [Swiss State Secretariat for Education, Research and Innovation](https://www.sbfi.admin.ch/sbfi/en/home.html) (SERI). Learn more on the [NLnet project page](https://nlnet.nl/project/PION-DTLS1.3/).
### License
MIT License - see [LICENSE](LICENSE) for full text

167
vendor/github.com/pion/dtls/v3/certificate.go generated vendored Normal file
View file

@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"strings"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
)
// ClientHelloInfo contains information from a ClientHello message in order to
// guide application logic in the GetCertificate.
type ClientHelloInfo struct {
// ServerName indicates the name of the server requested by the client
// in order to support virtual hosting. ServerName is only set if the
// client is using SNI (see RFC 4366, Section 3.1).
ServerName string
// CipherSuites lists the CipherSuites supported by the client (e.g.
// TLS_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256).
CipherSuites []CipherSuiteID
// RandomBytes stores the client hello random bytes
RandomBytes [handshake.RandomBytesLength]byte
}
// CertificateRequestInfo contains information from a server's
// CertificateRequest message, which is used to demand a certificate and proof
// of control from a client.
type CertificateRequestInfo struct {
// AcceptableCAs contains zero or more, DER-encoded, X.501
// Distinguished Names. These are the names of root or intermediate CAs
// that the server wishes the returned certificate to be signed by. An
// empty slice indicates that the server has no preference.
AcceptableCAs [][]byte
}
// SupportsCertificate returns nil if the provided certificate is supported by
// the server that sent the CertificateRequest. Otherwise, it returns an error
// describing the reason for the incompatibility.
// NOTE: original src:
// https://github.com/golang/go/blob/29b9a328d268d53833d2cc063d1d8b4bf6852675/src/crypto/tls/common.go#L1273
func (cri *CertificateRequestInfo) SupportsCertificate(c *tls.Certificate) error {
if len(cri.AcceptableCAs) == 0 {
return nil
}
for j, cert := range c.Certificate {
x509Cert := c.Leaf
// Parse the certificate if this isn't the leaf node, or if
// chain.Leaf was nil.
if j != 0 || x509Cert == nil {
var err error
if x509Cert, err = x509.ParseCertificate(cert); err != nil {
return fmt.Errorf("failed to parse certificate #%d in the chain: %w", j, err)
}
}
for _, ca := range cri.AcceptableCAs {
if bytes.Equal(x509Cert.RawIssuer, ca) {
return nil
}
}
}
return errNotAcceptableCertificateChain
}
func (c *handshakeConfig) setNameToCertificateLocked() {
nameToCertificate := make(map[string]*tls.Certificate)
for i := range c.localCertificates {
cert := &c.localCertificates[i]
x509Cert := cert.Leaf
if x509Cert == nil {
var parseErr error
x509Cert, parseErr = x509.ParseCertificate(cert.Certificate[0])
if parseErr != nil {
continue
}
}
if len(x509Cert.Subject.CommonName) > 0 {
nameToCertificate[strings.ToLower(x509Cert.Subject.CommonName)] = cert
}
for _, san := range x509Cert.DNSNames {
nameToCertificate[strings.ToLower(san)] = cert
}
}
c.nameToCertificate = nameToCertificate
}
//nolint:cyclop
func (c *handshakeConfig) getCertificate(clientHelloInfo *ClientHelloInfo) (*tls.Certificate, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.localGetCertificate != nil &&
(len(c.localCertificates) == 0 || len(clientHelloInfo.ServerName) > 0) {
cert, err := c.localGetCertificate(clientHelloInfo)
if cert != nil || err != nil {
return cert, err
}
}
if c.nameToCertificate == nil {
c.setNameToCertificateLocked()
}
if len(c.localCertificates) == 0 {
return nil, errNoCertificates
}
if len(c.localCertificates) == 1 {
// There's only one choice, so no point doing any work.
return &c.localCertificates[0], nil
}
if len(clientHelloInfo.ServerName) == 0 {
return &c.localCertificates[0], nil
}
name := strings.TrimRight(strings.ToLower(clientHelloInfo.ServerName), ".")
if cert, ok := c.nameToCertificate[name]; ok {
return cert, nil
}
// try replacing labels in the name with wildcards until we get a
// match.
labels := strings.Split(name, ".")
for i := range labels {
labels[i] = "*"
candidate := strings.Join(labels, ".")
if cert, ok := c.nameToCertificate[candidate]; ok {
return cert, nil
}
}
// If nothing matches, return the first certificate.
return &c.localCertificates[0], nil
}
// NOTE: original src:
// https://github.com/golang/go/blob/29b9a328d268d53833d2cc063d1d8b4bf6852675/src/crypto/tls/handshake_client.go#L974
func (c *handshakeConfig) getClientCertificate(cri *CertificateRequestInfo) (*tls.Certificate, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.localGetClientCertificate != nil {
return c.localGetClientCertificate(cri)
}
for i := range c.localCertificates {
chain := c.localCertificates[i]
if err := cri.SupportsCertificate(&chain); err != nil {
continue
}
return &chain, nil
}
// No acceptable certificate found. Don't send a certificate.
return new(tls.Certificate), nil
}

295
vendor/github.com/pion/dtls/v3/cipher_suite.go generated vendored Normal file
View file

@ -0,0 +1,295 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"fmt"
"hash"
"github.com/pion/dtls/v3/internal/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// CipherSuiteID is an ID for our supported CipherSuites.
type CipherSuiteID = ciphersuite.ID
// Supported Cipher Suites.
const (
// nolint: godot
// AES-128-CCM
TLS_ECDHE_ECDSA_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM // nolint: revive,staticcheck,lll
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 // nolint: revive,staticcheck,lll
// nolint: godot
// AES-128-GCM-SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 // nolint: revive,staticcheck,lll
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 // nolint: revive,staticcheck,lll
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 // nolint: revive,staticcheck,lll
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 // nolint: revive,staticcheck,lll
// nolint: godot
// AES-256-CBC-SHA
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA // nolint: revive,staticcheck,lll
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA // nolint: revive,staticcheck,lll
TLS_PSK_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM // nolint: revive,staticcheck,lll
TLS_PSK_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM_8 // nolint: revive,staticcheck,lll
TLS_PSK_WITH_AES_256_CCM_8 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_256_CCM_8 // nolint: revive,staticcheck,lll
TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_GCM_SHA256 // nolint: revive,staticcheck,lll
TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CBC_SHA256 // nolint: revive,staticcheck,lll
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 // nolint: revive,staticcheck,lll
)
// CipherSuiteAuthenticationType controls what authentication method is using during the handshake for a CipherSuite.
type CipherSuiteAuthenticationType = ciphersuite.AuthenticationType
// AuthenticationType Enums.
const (
CipherSuiteAuthenticationTypeCertificate CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypeCertificate
CipherSuiteAuthenticationTypePreSharedKey CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypePreSharedKey
CipherSuiteAuthenticationTypeAnonymous CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypeAnonymous
)
// CipherSuiteKeyExchangeAlgorithm controls what exchange algorithm is using during the handshake for a CipherSuite.
type CipherSuiteKeyExchangeAlgorithm = ciphersuite.KeyExchangeAlgorithm
// CipherSuiteKeyExchangeAlgorithm Bitmask.
const (
CipherSuiteKeyExchangeAlgorithmNone CipherSuiteKeyExchangeAlgorithm = ciphersuite.KeyExchangeAlgorithmNone
CipherSuiteKeyExchangeAlgorithmPsk CipherSuiteKeyExchangeAlgorithm = ciphersuite.KeyExchangeAlgorithmPsk
CipherSuiteKeyExchangeAlgorithmEcdhe CipherSuiteKeyExchangeAlgorithm = ciphersuite.KeyExchangeAlgorithmEcdhe
)
var _ = allCipherSuites() // Necessary until this function isn't only used by Go 1.14
// CipherSuite is an interface that all DTLS CipherSuites must satisfy.
type CipherSuite interface {
// String of CipherSuite, only used for logging
String() string
// ID of CipherSuite.
ID() CipherSuiteID
// What type of Certificate does this CipherSuite use
CertificateType() clientcertificate.Type
// What Hash function is used during verification
HashFunc() func() hash.Hash
// AuthenticationType controls what authentication method is using during the handshake
AuthenticationType() CipherSuiteAuthenticationType
// KeyExchangeAlgorithm controls what exchange algorithm is using during the handshake
KeyExchangeAlgorithm() CipherSuiteKeyExchangeAlgorithm
// ECC (Elliptic Curve Cryptography) determines whether ECC extesions will be send during handshake.
// https://datatracker.ietf.org/doc/html/rfc4492#page-10
ECC() bool
// Called when keying material has been generated, should initialize the internal cipher
Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error
IsInitialized() bool
Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error)
Decrypt(h recordlayer.Header, in []byte) ([]byte, error)
}
// CipherSuiteName provides the same functionality as tls.CipherSuiteName
// that appeared first in Go 1.14.
//
// Our implementation differs slightly in that it takes in a CiperSuiteID,
// like the rest of our library, instead of a uint16 like crypto/tls.
func CipherSuiteName(id CipherSuiteID) string {
suite := cipherSuiteForID(id, nil)
if suite != nil {
return suite.String()
}
return fmt.Sprintf("0x%04X", uint16(id))
}
// Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
// A cipherSuite is a specific combination of key agreement, cipher and MAC
// function.
func cipherSuiteForID(id CipherSuiteID, customCiphers func() []CipherSuite) CipherSuite { //nolint:cyclop
switch id { //nolint:exhaustive
case TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm()
case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8()
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
return &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
return &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
return &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{}
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
return &ciphersuite.TLSEcdheRsaWithAes256CbcSha{}
case TLS_PSK_WITH_AES_128_CCM:
return ciphersuite.NewTLSPskWithAes128Ccm()
case TLS_PSK_WITH_AES_128_CCM_8:
return ciphersuite.NewTLSPskWithAes128Ccm8()
case TLS_PSK_WITH_AES_256_CCM_8:
return ciphersuite.NewTLSPskWithAes256Ccm8()
case TLS_PSK_WITH_AES_128_GCM_SHA256:
return &ciphersuite.TLSPskWithAes128GcmSha256{}
case TLS_PSK_WITH_AES_128_CBC_SHA256:
return &ciphersuite.TLSPskWithAes128CbcSha256{}
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
return &ciphersuite.TLSEcdheEcdsaWithAes256GcmSha384{}
case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
return &ciphersuite.TLSEcdheRsaWithAes256GcmSha384{}
case TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
return ciphersuite.NewTLSEcdhePskWithAes128CbcSha256()
}
if customCiphers != nil {
for _, c := range customCiphers() {
if c.ID() == id {
return c
}
}
}
return nil
}
// CipherSuites we support in order of preference.
func defaultCipherSuites() []CipherSuite {
return []CipherSuite{
&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
&ciphersuite.TLSEcdheRsaWithAes128GcmSha256{},
&ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{},
&ciphersuite.TLSEcdheRsaWithAes256CbcSha{},
&ciphersuite.TLSEcdheEcdsaWithAes256GcmSha384{},
&ciphersuite.TLSEcdheRsaWithAes256GcmSha384{},
}
}
func allCipherSuites() []CipherSuite {
return []CipherSuite{
ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm(),
ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8(),
&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
&ciphersuite.TLSEcdheRsaWithAes128GcmSha256{},
&ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{},
&ciphersuite.TLSEcdheRsaWithAes256CbcSha{},
ciphersuite.NewTLSPskWithAes128Ccm(),
ciphersuite.NewTLSPskWithAes128Ccm8(),
ciphersuite.NewTLSPskWithAes256Ccm8(),
&ciphersuite.TLSPskWithAes128GcmSha256{},
&ciphersuite.TLSEcdheEcdsaWithAes256GcmSha384{},
&ciphersuite.TLSEcdheRsaWithAes256GcmSha384{},
}
}
func cipherSuiteIDs(cipherSuites []CipherSuite) []uint16 {
rtrn := []uint16{}
for _, c := range cipherSuites {
rtrn = append(rtrn, uint16(c.ID()))
}
return rtrn
}
//nolint:cyclop
func parseCipherSuites(
userSelectedSuites []CipherSuiteID,
customCipherSuites func() []CipherSuite,
includeCertificateSuites, includePSKSuites bool,
) ([]CipherSuite, error) {
cipherSuitesForIDs := func(ids []CipherSuiteID) ([]CipherSuite, error) {
cipherSuites := []CipherSuite{}
for _, id := range ids {
c := cipherSuiteForID(id, nil)
if c == nil {
return nil, &invalidCipherSuiteError{id}
}
cipherSuites = append(cipherSuites, c)
}
return cipherSuites, nil
}
var (
cipherSuites []CipherSuite
err error
i int
)
if userSelectedSuites != nil {
cipherSuites, err = cipherSuitesForIDs(userSelectedSuites)
if err != nil {
return nil, err
}
} else {
cipherSuites = defaultCipherSuites()
}
// Put CustomCipherSuites before ID selected suites
if customCipherSuites != nil {
cipherSuites = append(customCipherSuites(), cipherSuites...)
}
var foundCertificateSuite, foundPSKSuite, foundAnonymousSuite bool
for _, c := range cipherSuites {
switch {
case includeCertificateSuites && c.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate:
foundCertificateSuite = true
case includePSKSuites && c.AuthenticationType() == CipherSuiteAuthenticationTypePreSharedKey:
foundPSKSuite = true
case c.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous:
foundAnonymousSuite = true
default:
continue
}
cipherSuites[i] = c
i++
}
switch {
case includeCertificateSuites && !foundCertificateSuite && !foundAnonymousSuite:
return nil, errNoAvailableCertificateCipherSuite
case includePSKSuites && !foundPSKSuite:
return nil, errNoAvailablePSKCipherSuite
case i == 0:
return nil, errNoAvailableCipherSuites
}
return cipherSuites[:i], nil
}
func filterCipherSuitesForCertificate(cert *tls.Certificate, cipherSuites []CipherSuite) []CipherSuite {
if cert == nil || cert.PrivateKey == nil {
return cipherSuites
}
signer, ok := cert.PrivateKey.(crypto.Signer)
if !ok {
return cipherSuites
}
var certType clientcertificate.Type
switch signer.Public().(type) {
case ed25519.PublicKey, *ecdsa.PublicKey:
certType = clientcertificate.ECDSASign
case *rsa.PublicKey:
certType = clientcertificate.RSASign
}
filtered := []CipherSuite{}
for _, c := range cipherSuites {
if c.AuthenticationType() != CipherSuiteAuthenticationTypeCertificate || certType == c.CertificateType() {
filtered = append(filtered, c)
}
}
return filtered
}

46
vendor/github.com/pion/dtls/v3/cipher_suite_go114.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build go1.14
// +build go1.14
package dtls
import (
"crypto/tls"
)
// VersionDTLS12 is the DTLS version in the same style as
// VersionTLSXX from crypto/tls.
const VersionDTLS12 = 0xfefd
// Convert from our cipherSuite interface to a tls.CipherSuite struct.
func toTLSCipherSuite(c CipherSuite) *tls.CipherSuite {
return &tls.CipherSuite{
ID: uint16(c.ID()),
Name: c.String(),
SupportedVersions: []uint16{VersionDTLS12},
Insecure: false,
}
}
// CipherSuites returns a list of cipher suites currently implemented by this
// package, excluding those with security issues, which are returned by
// InsecureCipherSuites.
func CipherSuites() []*tls.CipherSuite {
suites := allCipherSuites()
res := make([]*tls.CipherSuite, len(suites))
for i, c := range suites {
res[i] = toTLSCipherSuite(c)
}
return res
}
// InsecureCipherSuites returns a list of cipher suites currently implemented by
// this package and which have security issues.
func InsecureCipherSuites() []*tls.CipherSuite {
var res []*tls.CipherSuite
return res
}

22
vendor/github.com/pion/dtls/v3/codecov.yml generated vendored Normal file
View file

@ -0,0 +1,22 @@
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
coverage:
status:
project:
default:
# Allow decreasing 2% of total coverage to avoid noise.
threshold: 2%
patch:
default:
target: 70%
only_pulls: true
ignore:
- "examples/*"
- "examples/**/*"

12
vendor/github.com/pion/dtls/v3/compression_method.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import "github.com/pion/dtls/v3/pkg/protocol"
func defaultCompressionMethods() []*protocol.CompressionMethod {
return []*protocol.CompressionMethod{
{},
}
}

306
vendor/github.com/pion/dtls/v3/config.go generated vendored Normal file
View file

@ -0,0 +1,306 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"io"
"net"
"time"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/logging"
)
const keyLogLabelTLS12 = "CLIENT_RANDOM"
// Config is used to configure a DTLS client or server.
// After a Config is passed to a DTLS function it must not be modified.
//
// Deprecated: prefer the options-based APIs (`*WithOptions`) to construct immutable configs,
// This will be removed in the next major version.
type Config struct { //nolint:dupl
// Certificates contains certificate chain to present to the other side of the connection.
// Server MUST set this if PSK is non-nil
// client SHOULD sets this so CertificateRequests can be handled if PSK is non-nil
Certificates []tls.Certificate
// CipherSuites is a list of supported cipher suites.
// If CipherSuites is nil, a default list is used
CipherSuites []CipherSuiteID
// CustomCipherSuites is a list of CipherSuites that can be
// provided by the user. This allow users to user Ciphers that are reserved
// for private usage.
CustomCipherSuites func() []CipherSuite
// SignatureSchemes contains the signature and hash schemes that the peer requests to verify.
SignatureSchemes []tls.SignatureScheme
// CertificateSignatureSchemes contains the signature and hash schemes that may be used
// in digital signatures for X.509 certificates. If not set, the signature_algorithms_cert
// extension is not sent, and SignatureSchemes is used for both handshake signatures and
// certificate chain validation, as specified in RFC 8446 Section 4.2.3.
CertificateSignatureSchemes []tls.SignatureScheme
// SRTPProtectionProfiles are the supported protection profiles
// Clients will send this via use_srtp and assert that the server properly responds
// Servers will assert that clients send one of these profiles and will respond as needed
SRTPProtectionProfiles []SRTPProtectionProfile
// SRTPMasterKeyIdentifier value (if any) is sent via the use_srtp
// extension for Clients and Servers
SRTPMasterKeyIdentifier []byte
// ClientAuth determines the server's policy for
// TLS Client Authentication. The default is NoClientCert.
ClientAuth ClientAuthType
// RequireExtendedMasterSecret determines if the "Extended Master Secret" extension
// should be disabled, requested, or required (default requested).
ExtendedMasterSecret ExtendedMasterSecretType
// FlightInterval controls how often we send outbound handshake messages
// defaults to time.Second
FlightInterval time.Duration
// DisableRetransmitBackoff can be used to the disable the backoff feature
// when sending outbound messages as specified in RFC 4347 4.2.4.1
DisableRetransmitBackoff bool
// PSK sets the pre-shared key used by this DTLS connection
// If PSK is non-nil only PSK CipherSuites will be used
PSK PSKCallback
PSKIdentityHint []byte
// InsecureSkipVerify controls whether a client verifies the
// server's certificate chain and host name.
// If InsecureSkipVerify is true, TLS accepts any certificate
// presented by the server and any host name in that certificate.
// In this mode, TLS is susceptible to man-in-the-middle attacks.
// This should be used only for testing.
InsecureSkipVerify bool
// InsecureHashes allows the use of hashing algorithms that are known
// to be vulnerable.
InsecureHashes bool
// VerifyPeerCertificate, if not nil, is called after normal
// certificate verification by either a client or server. It
// receives the certificate provided by the peer and also a flag
// that tells if normal verification has succeedded. If it returns a
// non-nil error, the handshake is aborted and that error results.
//
// If normal verification fails then the handshake will abort before
// considering this callback. If normal verification is disabled by
// setting InsecureSkipVerify, or (for a server) when ClientAuth is
// RequestClientCert or RequireAnyClientCert, then this callback will
// be considered but the verifiedChains will always be nil.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
// VerifyConnection, if not nil, is called after normal certificate
// verification/PSK and after VerifyPeerCertificate by either a TLS client
// or server. If it returns a non-nil error, the handshake is aborted
// and that error results.
//
// If normal verification fails then the handshake will abort before
// considering this callback. This callback will run for all connections
// regardless of InsecureSkipVerify or ClientAuth settings.
VerifyConnection func(*State) error
// RootCAs defines the set of root certificate authorities
// that one peer uses when verifying the other peer's certificates.
// If RootCAs is nil, TLS uses the host's root CA set.
RootCAs *x509.CertPool
// ClientCAs defines the set of root certificate authorities
// that servers use if required to verify a client certificate
// by the policy in ClientAuth.
ClientCAs *x509.CertPool
// ServerName is used to verify the hostname on the returned
// certificates unless InsecureSkipVerify is given.
ServerName string
LoggerFactory logging.LoggerFactory
// MTU is the length at which handshake messages will be fragmented to
// fit within the maximum transmission unit (default is 1200 bytes)
MTU int
// ReplayProtectionWindow is the size of the replay attack protection window.
// Duplication of the sequence number is checked in this window size.
// Packet with sequence number older than this value compared to the latest
// accepted packet will be discarded. (default is 64)
ReplayProtectionWindow int
// KeyLogWriter optionally specifies a destination for TLS master secrets
// in NSS key log format that can be used to allow external programs
// such as Wireshark to decrypt TLS connections.
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
// Use of KeyLogWriter compromises security and should only be
// used for debugging.
KeyLogWriter io.Writer
// SessionStore is the container to store session for resumption.
SessionStore SessionStore
// List of application protocols the peer supports, for ALPN
SupportedProtocols []string
// List of Elliptic Curves to use
//
// If an ECC ciphersuite is configured and EllipticCurves is empty
// it will default to X25519, P-256, P-384 in this specific order.
EllipticCurves []elliptic.Curve
// GetCertificate returns a Certificate based on the given
// ClientHelloInfo. It will only be called if the client supplies SNI
// information or if Certificates is empty.
//
// If GetCertificate is nil or returns nil, then the certificate is
// retrieved from NameToCertificate. If NameToCertificate is nil, the
// best element of Certificates will be used.
GetCertificate func(*ClientHelloInfo) (*tls.Certificate, error)
// GetClientCertificate, if not nil, is called when a server requests a
// certificate from a client. If set, the contents of Certificates will
// be ignored.
//
// If GetClientCertificate returns an error, the handshake will be
// aborted and that error will be returned. Otherwise
// GetClientCertificate must return a non-nil Certificate. If
// Certificate.Certificate is empty then no certificate will be sent to
// the server. If this is unacceptable to the server then it may abort
// the handshake.
GetClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error)
// InsecureSkipVerifyHello, if true and when acting as server, allow client to
// skip hello verify phase and receive ServerHello after initial ClientHello.
// This have implication on DoS attack resistance.
InsecureSkipVerifyHello bool
// ConnectionIDGenerator generates connection identifiers that should be
// sent by the remote party if it supports the DTLS Connection Identifier
// extension, as determined during the handshake. Generated connection
// identifiers must always have the same length. Returning a zero-length
// connection identifier indicates that the local party supports sending
// connection identifiers but does not require the remote party to send
// them. A nil ConnectionIDGenerator indicates that connection identifiers
// are not supported.
// https://datatracker.ietf.org/doc/html/rfc9146
ConnectionIDGenerator func() []byte
// PaddingLengthGenerator generates the number of padding bytes used to
// inflate ciphertext size in order to obscure content size from observers.
// The length of the content is passed to the generator such that both
// deterministic and random padding schemes can be applied while not
// exceeding maximum record size.
// If no PaddingLengthGenerator is specified, padding will not be applied.
// https://datatracker.ietf.org/doc/html/rfc9146#section-4
PaddingLengthGenerator func(uint) uint
// HelloRandomBytesGenerator generates custom client hello random bytes.
HelloRandomBytesGenerator func() [handshake.RandomBytesLength]byte
// Handshake hooks: hooks can be used for testing invalid messages,
// mimicking other implementations or randomizing fields, which is valuable
// for applications that need censorship-resistance by making
// fingerprinting more difficult.
// ClientHelloMessageHook, if not nil, is called when a Client Hello message is sent
// from a client. The returned handshake message replaces the original message.
ClientHelloMessageHook func(handshake.MessageClientHello) handshake.Message
// ServerHelloMessageHook, if not nil, is called when a Server Hello message is sent
// from a server. The returned handshake message replaces the original message.
ServerHelloMessageHook func(handshake.MessageServerHello) handshake.Message
// CertificateRequestMessageHook, if not nil, is called when a Certificate Request
// message is sent from a server. The returned handshake message replaces the original message.
CertificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message
// OnConnectionAttempt is fired Whenever a connection attempt is made,
// the server or application can call this callback function.
// The callback function can then implement logic to handle the connection attempt, such as logging the attempt,
// checking against a list of blocked IPs, or counting the attempts to prevent brute force attacks.
// If the callback function returns an error, the connection attempt will be aborted.
OnConnectionAttempt func(net.Addr) error
}
func (c *Config) includeCertificateSuites() bool {
return c.PSK == nil || len(c.Certificates) > 0 || c.GetCertificate != nil || c.GetClientCertificate != nil
}
const defaultMTU = 1200 // bytes
var defaultCurves = []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384} //nolint:gochecknoglobals
// PSKCallback is called once we have the remote's PSKIdentityHint.
// If the remote provided none it will be nil.
type PSKCallback func([]byte) ([]byte, error)
// ClientAuthType declares the policy the server will follow for
// TLS Client Authentication.
type ClientAuthType int
// ClientAuthType enums.
const (
NoClientCert ClientAuthType = iota
RequestClientCert
RequireAnyClientCert
VerifyClientCertIfGiven
RequireAndVerifyClientCert
)
// ExtendedMasterSecretType declares the policy the client and server
// will follow for the Extended Master Secret extension.
type ExtendedMasterSecretType int
// ExtendedMasterSecretType enums.
const (
RequestExtendedMasterSecret ExtendedMasterSecretType = iota
RequireExtendedMasterSecret
DisableExtendedMasterSecret
)
func validateConfig(config *Config) error { //nolint:cyclop
switch {
case config == nil:
return errNoConfigProvided
case config.PSKIdentityHint != nil && config.PSK == nil:
return errIdentityNoPSK
}
for _, cert := range config.Certificates {
if cert.Certificate == nil {
return errInvalidCertificate
}
if cert.PrivateKey != nil {
signer, ok := cert.PrivateKey.(crypto.Signer)
if !ok {
return errInvalidPrivateKey
}
switch signer.Public().(type) {
case ed25519.PublicKey:
case *ecdsa.PublicKey:
case *rsa.PublicKey:
default:
return errInvalidPrivateKey
}
}
}
_, err := parseCipherSuites(
config.CipherSuites, config.CustomCipherSuites, config.includeCertificateSuites(), config.PSK != nil,
)
return err
}

1397
vendor/github.com/pion/dtls/v3/conn.go generated vendored Normal file

File diff suppressed because it is too large Load diff

105
vendor/github.com/pion/dtls/v3/connection_id.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"crypto/rand"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// RandomCIDGenerator is a random Connection ID generator where CID is the
// specified size. Specifying a size of 0 will indicate to peers that sending a
// Connection ID is not necessary.
func RandomCIDGenerator(size int) func() []byte {
return func() []byte {
cid := make([]byte, size)
if _, err := rand.Read(cid); err != nil {
panic(err) //nolint -- nonrecoverable
}
return cid
}
}
// OnlySendCIDGenerator enables sending Connection IDs negotiated with a peer,
// but indicates to the peer that sending Connection IDs in return is not
// necessary.
func OnlySendCIDGenerator() func() []byte {
return func() []byte {
return nil
}
}
// cidDatagramRouter extracts connection IDs from incoming datagram payloads and
// uses them to route to the proper connection.
// NOTE: properly routing datagrams based on connection IDs requires using
// constant size connection IDs.
func cidDatagramRouter(size int) func([]byte) (string, bool) {
return func(packet []byte) (string, bool) {
pkts, err := recordlayer.ContentAwareUnpackDatagram(packet, size)
if err != nil || len(pkts) < 1 {
return "", false
}
for _, pkt := range pkts {
h := &recordlayer.Header{
ConnectionID: make([]byte, size),
}
if err := h.Unmarshal(pkt); err != nil {
continue
}
if h.ContentType != protocol.ContentTypeConnectionID {
continue
}
return string(h.ConnectionID), true
}
return "", false
}
}
// cidConnIdentifier extracts connection IDs from outgoing ServerHello records
// and associates them with the associated connection.
// NOTE: a ServerHello should always be the first record in a datagram if
// multiple are present, so we avoid iterating through all packets if the first
// is not a ServerHello.
func cidConnIdentifier() func([]byte) (string, bool) { //nolint:cyclop
return func(packet []byte) (string, bool) {
pkts, err := recordlayer.UnpackDatagram(packet)
if err != nil || len(pkts) < 1 {
return "", false
}
var h recordlayer.Header
if hErr := h.Unmarshal(pkts[0]); hErr != nil {
return "", false
}
if h.ContentType != protocol.ContentTypeHandshake {
return "", false
}
var hh handshake.Header
var sh handshake.MessageServerHello
for _, pkt := range pkts {
if hhErr := hh.Unmarshal(pkt[recordlayer.FixedHeaderSize:]); hhErr != nil {
continue
}
if err = sh.Unmarshal(pkt[recordlayer.FixedHeaderSize+handshake.HeaderLength:]); err == nil {
break
}
}
if err != nil {
return "", false
}
for _, ext := range sh.Extensions {
if e, ok := ext.(*extension.ConnectionID); ok {
return string(e.CID), true
}
}
return "", false
}
}

457
vendor/github.com/pion/dtls/v3/crypto.go generated vendored Normal file
View file

@ -0,0 +1,457 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/binary"
"math/big"
"time"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/hash"
"github.com/pion/dtls/v3/pkg/crypto/signature"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
)
type ecdsaSignature struct {
R, S *big.Int
}
func valueKeyMessage(clientRandom, serverRandom, publicKey []byte, namedCurve elliptic.Curve) []byte {
serverECDHParams := make([]byte, 4)
serverECDHParams[0] = 3 // named curve
binary.BigEndian.PutUint16(serverECDHParams[1:], uint16(namedCurve))
serverECDHParams[3] = byte(len(publicKey))
plaintext := []byte{}
plaintext = append(plaintext, clientRandom...)
plaintext = append(plaintext, serverRandom...)
plaintext = append(plaintext, serverECDHParams...)
plaintext = append(plaintext, publicKey...)
return plaintext
}
// validateSignatureAlgOID validates that the signature scheme matches the
// certificate's public key algorithm OID. This is required by RFC 8446 Section 4.2.3:
// - RSA_PSS_RSAE requires rsaEncryption OID
// - RSA_PSS_PSS requires id-RSASSA-PSS OID
//
// Note: returns nil if the given signature.Algorithm is not PSS based.
//
// https://www.rfc-editor.org/rfc/rfc8446#section-4.2.3
func validateSignatureAlgOID(cert *x509.Certificate, sigAlg signature.Algorithm) error {
if !sigAlg.IsPSS() {
return nil
}
// Get the certificate's public key algorithm OID from the raw certificate
// We need to parse the SubjectPublicKeyInfo to get the algorithm OID
var spki struct {
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
if _, err := asn1.Unmarshal(cert.RawSubjectPublicKeyInfo, &spki); err != nil {
return err
}
certOID := spki.Algorithm.Algorithm
switch sigAlg {
// Check RSAE variants (0x0804-0x0806) require rsaEncryption OID
case signature.RSA_PSS_RSAE_SHA256, signature.RSA_PSS_RSAE_SHA384, signature.RSA_PSS_RSAE_SHA512:
oidPublicKeyRSA := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} // OID: rsaEncryption
if !certOID.Equal(oidPublicKeyRSA) {
return errInvalidCertificateOID
}
return nil
// Check PSS variants (0x0809-0x080b) require id-RSASSA-PSS OID
case signature.RSA_PSS_PSS_SHA256, signature.RSA_PSS_PSS_SHA384, signature.RSA_PSS_PSS_SHA512:
oidPublicKeyRSAPSS := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} // OID: id-RSASSA-PSS
if !certOID.Equal(oidPublicKeyRSAPSS) {
return errInvalidCertificateOID
}
return nil
default:
return nil
}
}
// If the client provided a "signature_algorithms" extension, then all
// certificates provided by the server MUST be signed by a
// hash/signature algorithm pair that appears in that extension
//
// https://tools.ietf.org/html/rfc5246#section-7.4.2
func generateKeySignature(
clientRandom, serverRandom, publicKey []byte,
namedCurve elliptic.Curve,
signer crypto.Signer,
hashAlgorithm hash.Algorithm,
signatureAlgorithm signature.Algorithm,
) ([]byte, error) {
msg := valueKeyMessage(clientRandom, serverRandom, publicKey, namedCurve)
switch signer.Public().(type) {
case ed25519.PublicKey:
// https://crypto.stackexchange.com/a/55483
return signer.Sign(rand.Reader, msg, crypto.Hash(0))
case *ecdsa.PublicKey:
hashed := hashAlgorithm.Digest(msg)
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PublicKey:
hashed := hashAlgorithm.Digest(msg)
// Use RSA-PSS if the signature algorithm is PSS
if signatureAlgorithm.IsPSS() {
pssOpts := &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: hashAlgorithm.CryptoHash(),
}
return signer.Sign(rand.Reader, hashed, pssOpts)
}
// Otherwise use PKCS#1 v1.5
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
}
return nil, errKeySignatureGenerateUnimplemented
}
//nolint:dupl,cyclop
func verifyKeySignature(
message, remoteKeySignature []byte,
hashAlgorithm hash.Algorithm,
signatureAlgorithm signature.Algorithm,
rawCertificates [][]byte,
) error {
if len(rawCertificates) == 0 {
return errLengthMismatch
}
certificate, err := x509.ParseCertificate(rawCertificates[0])
if err != nil {
return err
}
// Validate that the signature algorithm matches the certificate's OID
if err := validateSignatureAlgOID(certificate, signatureAlgorithm); err != nil {
return err
}
switch pubKey := certificate.PublicKey.(type) {
case ed25519.PublicKey:
if ok := ed25519.Verify(pubKey, message, remoteKeySignature); !ok {
return errKeySignatureMismatch
}
return nil
case *ecdsa.PublicKey:
ecdsaSig := &ecdsaSignature{}
if _, err := asn1.Unmarshal(remoteKeySignature, ecdsaSig); err != nil {
return err
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return errInvalidECDSASignature
}
hashed := hashAlgorithm.Digest(message)
if !ecdsa.Verify(pubKey, hashed, ecdsaSig.R, ecdsaSig.S) {
return errKeySignatureMismatch
}
return nil
case *rsa.PublicKey:
hashed := hashAlgorithm.Digest(message)
// Use RSA-PSS verification if the signature algorithm is PSS
if signatureAlgorithm.IsPSS() {
pssOpts := &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: hashAlgorithm.CryptoHash(),
}
if err := rsa.VerifyPSS(pubKey, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature, pssOpts); err != nil {
return errKeySignatureMismatch
}
return nil
}
// Otherwise use PKCS#1 v1.5
if rsa.VerifyPKCS1v15(pubKey, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature) != nil {
return errKeySignatureMismatch
}
return nil
}
return errKeySignatureVerifyUnimplemented
}
// If the server has sent a CertificateRequest message, the client MUST send the Certificate
// message. The ClientKeyExchange message is now sent, and the content
// of that message will depend on the public key algorithm selected
// between the ClientHello and the ServerHello. If the client has sent
// a certificate with signing ability, a digitally-signed
// CertificateVerify message is sent to explicitly verify possession of
// the private key in the certificate.
// https://tools.ietf.org/html/rfc5246#section-7.3
func generateCertificateVerify(
handshakeBodies []byte,
signer crypto.Signer,
hashAlgorithm hash.Algorithm,
signatureAlgorithm signature.Algorithm,
) ([]byte, error) {
if _, ok := signer.Public().(ed25519.PublicKey); ok {
// https://pkg.go.dev/crypto/ed25519#PrivateKey.Sign
// Sign signs the given message with priv. Ed25519 performs two passes over
// messages to be signed and therefore cannot handle pre-hashed messages.
return signer.Sign(rand.Reader, handshakeBodies, crypto.Hash(0))
}
hashed := hashAlgorithm.Digest(handshakeBodies)
switch signer.Public().(type) {
case *ecdsa.PublicKey:
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PublicKey:
// Use RSA-PSS if the signature algorithm is PSS
if signatureAlgorithm.IsPSS() {
pssOpts := &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: hashAlgorithm.CryptoHash(),
}
return signer.Sign(rand.Reader, hashed, pssOpts)
}
// Otherwise use PKCS#1 v1.5
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
}
return nil, errInvalidSignatureAlgorithm
}
//nolint:dupl,cyclop
func verifyCertificateVerify(
handshakeBodies []byte,
hashAlgorithm hash.Algorithm,
signatureAlgorithm signature.Algorithm,
remoteKeySignature []byte,
rawCertificates [][]byte,
) error {
if len(rawCertificates) == 0 {
return errLengthMismatch
}
certificate, err := x509.ParseCertificate(rawCertificates[0])
if err != nil {
return err
}
// Validate that the signature algorithm matches the certificate's OID
if err := validateSignatureAlgOID(certificate, signatureAlgorithm); err != nil {
return err
}
switch pubKey := certificate.PublicKey.(type) {
case ed25519.PublicKey:
if ok := ed25519.Verify(pubKey, handshakeBodies, remoteKeySignature); !ok {
return errKeySignatureMismatch
}
return nil
case *ecdsa.PublicKey:
ecdsaSig := &ecdsaSignature{}
if _, err := asn1.Unmarshal(remoteKeySignature, ecdsaSig); err != nil {
return err
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return errInvalidECDSASignature
}
hash := hashAlgorithm.Digest(handshakeBodies)
if !ecdsa.Verify(pubKey, hash, ecdsaSig.R, ecdsaSig.S) {
return errKeySignatureMismatch
}
return nil
case *rsa.PublicKey:
hash := hashAlgorithm.Digest(handshakeBodies)
// Use RSA-PSS verification if the signature algorithm is PSS
if signatureAlgorithm.IsPSS() {
pssOpts := &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: hashAlgorithm.CryptoHash(),
}
if err := rsa.VerifyPSS(pubKey, hashAlgorithm.CryptoHash(), hash, remoteKeySignature, pssOpts); err != nil {
return errKeySignatureMismatch
}
return nil
}
// Otherwise use PKCS#1 v1.5
if rsa.VerifyPKCS1v15(pubKey, hashAlgorithm.CryptoHash(), hash, remoteKeySignature) != nil {
return errKeySignatureMismatch
}
return nil
}
return errKeySignatureVerifyUnimplemented
}
func loadCerts(rawCertificates [][]byte) ([]*x509.Certificate, error) {
if len(rawCertificates) == 0 {
return nil, errLengthMismatch
}
certs := make([]*x509.Certificate, 0, len(rawCertificates))
for _, rawCert := range rawCertificates {
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return nil, err
}
certs = append(certs, cert)
}
return certs, nil
}
func verifyClientCert(
rawCertificates [][]byte,
roots *x509.CertPool,
certSignatureSchemes []signaturehash.Algorithm,
) (chains [][]*x509.Certificate, err error) {
certificate, err := loadCerts(rawCertificates)
if err != nil {
return nil, err
}
intermediateCAPool := x509.NewCertPool()
for _, cert := range certificate[1:] {
intermediateCAPool.AddCert(cert)
}
opts := x509.VerifyOptions{
Roots: roots,
CurrentTime: time.Now(),
Intermediates: intermediateCAPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
chains, err = certificate[0].Verify(opts)
if err != nil {
return nil, err
}
// Validate certificate signature algorithms if specified.
// At least one chain must use only allowed signature algorithms.
if len(certSignatureSchemes) > 0 && len(chains) > 0 {
var validChainFound bool
for _, chain := range chains {
if err := validateCertificateSignatureAlgorithms(chain, certSignatureSchemes); err == nil {
validChainFound = true
break
}
}
if !validChainFound {
return nil, errInvalidCertificateSignatureAlgorithm
}
}
return chains, nil
}
func verifyServerCert(
rawCertificates [][]byte,
roots *x509.CertPool,
serverName string,
certSignatureSchemes []signaturehash.Algorithm,
) (chains [][]*x509.Certificate, err error) {
certificate, err := loadCerts(rawCertificates)
if err != nil {
return nil, err
}
intermediateCAPool := x509.NewCertPool()
for _, cert := range certificate[1:] {
intermediateCAPool.AddCert(cert)
}
opts := x509.VerifyOptions{
Roots: roots,
CurrentTime: time.Now(),
DNSName: serverName,
Intermediates: intermediateCAPool,
}
chains, err = certificate[0].Verify(opts)
if err != nil {
return nil, err
}
// Validate certificate signature algorithms if specified.
// At least one chain must use only allowed signature algorithms.
if len(certSignatureSchemes) > 0 && len(chains) > 0 {
var validChainFound bool
for _, chain := range chains {
if err := validateCertificateSignatureAlgorithms(chain, certSignatureSchemes); err == nil {
validChainFound = true
break
}
}
if !validChainFound {
return nil, errInvalidCertificateSignatureAlgorithm
}
}
return chains, nil
}
// validateCertificateSignatureAlgorithms validates that all certificates in the chain
// use signature algorithms that are in the allowed list. This implements the
// signature_algorithms_cert extension validation per RFC 8446 Section 4.2.3.
func validateCertificateSignatureAlgorithms(
certs []*x509.Certificate,
allowedAlgorithms []signaturehash.Algorithm,
) error {
if len(allowedAlgorithms) == 0 {
// No restrictions specified
return nil
}
// Validate each certificate's signature algorithm (except the root, which we trust)
for i := 0; i < len(certs)-1; i++ {
cert := certs[i]
certAlg, err := signaturehash.FromCertificate(cert)
if err != nil {
return err
}
// Check if this algorithm is in the allowed list
found := false
for _, allowed := range allowedAlgorithms {
if certAlg.Hash == allowed.Hash && certAlg.Signature == allowed.Signature {
found = true
break
}
}
if !found {
return errInvalidCertificateSignatureAlgorithm
}
}
return nil
}

5
vendor/github.com/pion/dtls/v3/dtls.go generated vendored Normal file
View file

@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package dtls implements Datagram Transport Layer Security (DTLS) 1.2
package dtls

308
vendor/github.com/pion/dtls/v3/errors.go generated vendored Normal file
View file

@ -0,0 +1,308 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
)
// Typed errors.
var (
ErrConnClosed = &FatalError{Err: errors.New("conn is closed")} //nolint:err113
errDeadlineExceeded = &TimeoutError{Err: fmt.Errorf("read/write timeout: %w", context.DeadlineExceeded)}
errInvalidContentType = &TemporaryError{Err: errors.New("invalid content type")} //nolint:err113
//nolint:err113
errBufferTooSmall = &TemporaryError{Err: errors.New("buffer is too small")}
//nolint:err113
errContextUnsupported = &TemporaryError{Err: errors.New("context is not supported for ExportKeyingMaterial")}
//nolint:err113
errHandshakeInProgress = &TemporaryError{Err: errors.New("handshake is in progress")}
//nolint:err113
errReservedExportKeyingMaterial = &TemporaryError{
Err: errors.New("ExportKeyingMaterial can not be used with a reserved label"),
}
//nolint:err113
errApplicationDataEpochZero = &TemporaryError{Err: errors.New("ApplicationData with epoch of 0")}
//nolint:err113
errUnhandledContextType = &TemporaryError{Err: errors.New("unhandled contentType")}
//nolint:err113
errCertificateVerifyNoCertificate = &FatalError{
Err: errors.New("client sent certificate verify but we have no certificate to verify"),
}
//nolint:err113
errCipherSuiteNoIntersection = &FatalError{Err: errors.New("client+server do not support any shared cipher suites")}
//nolint:err113
errClientCertificateNotVerified = &FatalError{Err: errors.New("client sent certificate but did not verify it")}
//nolint:err113
errClientCertificateRequired = &FatalError{Err: errors.New("server required client verification, but got none")}
//nolint:err113
errClientNoMatchingSRTPProfile = &FatalError{Err: errors.New("server responded with SRTP Profile we do not support")}
//nolint:err113
errClientRequiredButNoServerEMS = &FatalError{
Err: errors.New("client required Extended Master Secret extension, but server does not support it"),
}
//nolint:err113
errCookieMismatch = &FatalError{Err: errors.New("client+server cookie does not match")}
//nolint:err113
errIdentityNoPSK = &FatalError{Err: errors.New("PSK Identity Hint provided but PSK is nil")}
//nolint:err113
errInvalidCertificate = &FatalError{Err: errors.New("no certificate provided")}
//nolint:err113
errInvalidCipherSuite = &FatalError{Err: errors.New("invalid or unknown cipher suite")}
//nolint:err113
errInvalidClientAuthType = &FatalError{Err: errors.New("invalid client auth type")}
//nolint:err113
errInvalidECDSASignature = &FatalError{Err: errors.New("ECDSA signature contained zero or negative values")}
//nolint:err113
errInvalidPrivateKey = &FatalError{Err: errors.New("invalid private key type")}
//nolint:err113
errInvalidSignatureAlgorithm = &FatalError{Err: errors.New("invalid signature algorithm")}
//nolint:err113
errInvalidExtendedMasterSecretType = &FatalError{Err: errors.New("invalid extended master secret type")}
//nolint:err113
errInvalidCertificateSignatureAlgorithm = &FatalError{
Err: errors.New("certificate uses a signature algorithm that is not allowed"),
}
//nolint:err113
errKeySignatureMismatch = &FatalError{Err: errors.New("expected and actual key signature do not match")}
//nolint:err113
errInvalidCertificateOID = &FatalError{Err: errors.New("certificate OID does not match signature algorithm")}
//nolint:err113
errNilNextConn = &FatalError{Err: errors.New("Conn can not be created with a nil nextConn")}
//nolint:err113
errNoAvailableCipherSuites = &FatalError{
Err: errors.New("connection can not be created, no CipherSuites satisfy this Config"),
}
//nolint:err113
errNoAvailablePSKCipherSuite = &FatalError{
Err: errors.New("connection can not be created, pre-shared key present but no compatible CipherSuite"),
}
//nolint:err113
errNoAvailableCertificateCipherSuite = &FatalError{
Err: errors.New("connection can not be created, certificate present but no compatible CipherSuite"),
}
//nolint:err113
errNoAvailableSignatureSchemes = &FatalError{
Err: errors.New("connection can not be created, no SignatureScheme satisfy this Config"),
}
//nolint:err113
errNoCertificates = &FatalError{Err: errors.New("no certificates configured")}
//nolint:err113
errNoConfigProvided = &FatalError{Err: errors.New("no config provided")}
//nolint:err113
errNoSupportedEllipticCurves = &FatalError{
Err: errors.New("client requested zero or more elliptic curves that are not supported by the server"),
}
//nolint:err113
errUnsupportedProtocolVersion = &FatalError{Err: errors.New("unsupported protocol version")}
//nolint:err113
errPSKAndIdentityMustBeSetForClient = &FatalError{
Err: errors.New("PSK and PSK Identity Hint must both be set for client"),
}
//nolint:err113
errRequestedButNoSRTPExtension = &FatalError{
Err: errors.New("SRTP support was requested but server did not respond with use_srtp extension"),
}
//nolint:err113
errServerNoMatchingSRTPProfile = &FatalError{Err: errors.New("client requested SRTP but we have no matching profiles")}
//nolint:err113
errServerRequiredButNoClientEMS = &FatalError{
Err: errors.New("server requires the Extended Master Secret extension, but the client does not support it"),
}
//nolint:err113
errVerifyDataMismatch = &FatalError{Err: errors.New("expected and actual verify data does not match")}
//nolint:err113
errNotAcceptableCertificateChain = &FatalError{Err: errors.New("certificate chain is not signed by an acceptable CA")}
//nolint:err113
errInvalidFlight = &InternalError{Err: errors.New("invalid flight number")}
//nolint:err113
errKeySignatureGenerateUnimplemented = &InternalError{
Err: errors.New("unable to generate key signature, unimplemented"),
}
//nolint:err113
errKeySignatureVerifyUnimplemented = &InternalError{Err: errors.New("unable to verify key signature, unimplemented")}
//nolint:err113
errLengthMismatch = &InternalError{Err: errors.New("data length and declared length do not match")}
//nolint:err113
errSequenceNumberOverflow = &InternalError{Err: errors.New("sequence number overflow")}
//nolint:err113
errInvalidFSMTransition = &InternalError{Err: errors.New("invalid state machine transition")}
//nolint:err113
errFailedToAccessPoolReadBuffer = &InternalError{Err: errors.New("failed to access pool read buffer")}
//nolint:err113
errFragmentBufferOverflow = &InternalError{Err: errors.New("fragment buffer overflow")}
//nolint:err113
errEmptyCertificates = &FatalError{Err: errors.New("certificates option requires at least one certificate")}
//nolint:err113
errEmptyCipherSuites = &FatalError{Err: errors.New("cipher suites option requires at least one cipher suite")}
//nolint:err113
errNilCustomCipherSuites = &FatalError{Err: errors.New("custom cipher suites option requires a non-nil function")}
//nolint:err113
errEmptySignatureSchemes = &FatalError{Err: errors.New("signature schemes option requires at least one scheme")}
//nolint:err113
errEmptyCertificateSignatureSchemes = &FatalError{
Err: errors.New("certificate signature schemes option requires at least one scheme"),
}
//nolint:err113
errEmptySRTPProtectionProfiles = &FatalError{
Err: errors.New("SRTP protection profiles option requires at least one profile"),
}
//nolint:err113
errInvalidFlightInterval = &FatalError{Err: errors.New("flight interval must be positive")}
//nolint:err113
errNilPSKCallback = &FatalError{Err: errors.New("PSK option requires a non-nil callback")}
//nolint:err113
errNilVerifyPeerCertificate = &FatalError{
Err: errors.New("verify peer certificate option requires a non-nil callback"),
}
//nolint:err113
errNilVerifyConnection = &FatalError{Err: errors.New("verify connection option requires a non-nil callback")}
//nolint:err113
errInvalidMTU = &FatalError{Err: errors.New("MTU must be positive")}
//nolint:err113
errInvalidReplayProtectionWindow = &FatalError{Err: errors.New("replay protection window must be non-negative")}
//nolint:err113
errEmptySupportedProtocols = &FatalError{
Err: errors.New("supported protocols option requires at least one protocol"),
}
//nolint:err113
errEmptyEllipticCurves = &FatalError{Err: errors.New("elliptic curves option requires at least one curve")}
//nolint:err113
errNilGetClientCertificate = &FatalError{
Err: errors.New("get client certificate option requires a non-nil callback"),
}
//nolint:err113
errNilConnectionIDGenerator = &FatalError{
Err: errors.New("connection ID generator option requires a non-nil function"),
}
//nolint:err113
errNilPaddingLengthGenerator = &FatalError{
Err: errors.New("padding length generator option requires a non-nil function"),
}
//nolint:err113
errNilHelloRandomBytesGenerator = &FatalError{
Err: errors.New("hello random bytes generator option requires a non-nil function"),
}
//nolint:err113
errNilClientHelloMessageHook = &FatalError{
Err: errors.New("client hello message hook option requires a non-nil function"),
}
//nolint:err113
errNilGetCertificate = &FatalError{Err: errors.New("get certificate option requires a non-nil callback")}
//nolint:err113
errNilServerHelloMessageHook = &FatalError{
Err: errors.New("server hello message hook option requires a non-nil function"),
}
//nolint:err113
errNilCertificateRequestMessageHook = &FatalError{
Err: errors.New("certificate request message hook option requires a non-nil function"),
}
//nolint:err113
errNilOnConnectionAttempt = &FatalError{
Err: errors.New("on connection attempt option requires a non-nil callback"),
}
)
// FatalError indicates that the DTLS connection is no longer available.
// It is mainly caused by wrong configuration of server or client.
type FatalError = protocol.FatalError
// InternalError indicates and internal error caused by the implementation,
// and the DTLS connection is no longer available.
// It is mainly caused by bugs or tried to use unimplemented features.
type InternalError = protocol.InternalError
// TemporaryError indicates that the DTLS connection is still available, but the request was failed temporary.
type TemporaryError = protocol.TemporaryError
// TimeoutError indicates that the request was timed out.
type TimeoutError = protocol.TimeoutError
// HandshakeError indicates that the handshake failed.
type HandshakeError = protocol.HandshakeError
// errInvalidCipherSuite indicates an attempt at using an unsupported cipher suite.
type invalidCipherSuiteError struct {
id CipherSuiteID
}
func (e *invalidCipherSuiteError) Error() string {
return fmt.Sprintf("CipherSuite with id(%d) is not valid", e.id)
}
func (e *invalidCipherSuiteError) Is(err error) bool {
var other *invalidCipherSuiteError
if errors.As(err, &other) {
return e.id == other.id
}
return false
}
// errAlert wraps DTLS alert notification as an error.
type alertError struct {
*alert.Alert
}
func (e *alertError) Error() string {
return fmt.Sprintf("alert: %s", e.Alert.String())
}
func (e *alertError) IsFatalOrCloseNotify() bool {
return e.Level == alert.Fatal || e.Description == alert.CloseNotify
}
func (e *alertError) Is(err error) bool {
var other *alertError
if errors.As(err, &other) {
return e.Level == other.Level && e.Description == other.Description
}
return false
}
// netError translates an error from underlying Conn to corresponding net.Error.
func netError(err error) error {
switch {
case errors.Is(err, io.EOF), errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
// Return io.EOF and context errors as is.
return err
}
var (
ne net.Error
opError *net.OpError
se *os.SyscallError
)
if errors.As(err, &opError) { //nolint:nestif
if errors.As(opError, &se) {
if se.Timeout() {
return &TimeoutError{Err: err}
}
if isOpErrorTemporary(se) {
return &TemporaryError{Err: err}
}
}
}
if errors.As(err, &ne) {
return err
}
return &FatalError{Err: err}
}

22
vendor/github.com/pion/dtls/v3/errors_errno.go generated vendored Normal file
View file

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build aix || darwin || dragonfly || freebsd || linux || nacl || nacljs || netbsd || openbsd || solaris || windows
// +build aix darwin dragonfly freebsd linux nacl nacljs netbsd openbsd solaris windows
// For systems having syscall.Errno.
// Update build targets by following command:
// $ grep -R ECONN $(go env GOROOT)/src/syscall/zerrors_*.go \
// | tr "." "_" | cut -d"_" -f"2" | sort | uniq
package dtls
import (
"errors"
"os"
"syscall"
)
func isOpErrorTemporary(err *os.SyscallError) bool {
return errors.Is(err.Err, syscall.ECONNREFUSED)
}

18
vendor/github.com/pion/dtls/v3/errors_noerrno.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !nacl && !nacljs && !netbsd && !openbsd && !solaris && !windows
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!nacl,!nacljs,!netbsd,!openbsd,!solaris,!windows
// For systems without syscall.Errno.
// Build targets must be inverse of errors_errno.go
package dtls
import (
"os"
)
func isOpErrorTemporary(err *os.SyscallError) bool {
return false
}

104
vendor/github.com/pion/dtls/v3/flight.go generated vendored Normal file
View file

@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
/*
DTLS messages are grouped into a series of message flights, according
to the diagrams below. Although each flight of messages may consist
of a number of messages, they should be viewed as monolithic for the
purpose of timeout and retransmission.
https://tools.ietf.org/html/rfc4347#section-4.2.4
Message flights for full handshake:
Client Server
------ ------
Waiting Flight 0
ClientHello --------> Flight 1
<------- HelloVerifyRequest Flight 2
ClientHello --------> Flight 3
ServerHello \
Certificate* \
ServerKeyExchange* Flight 4
CertificateRequest* /
<-------- ServerHelloDone /
Certificate* \
ClientKeyExchange \
CertificateVerify* Flight 5
[ChangeCipherSpec] /
Finished --------> /
[ChangeCipherSpec] \ Flight 6
<-------- Finished /
Message flights for session-resuming handshake (no cookie exchange):
Client Server
------ ------
Waiting Flight 0
ClientHello --------> Flight 1
ServerHello \
[ChangeCipherSpec] Flight 4b
<-------- Finished /
[ChangeCipherSpec] \ Flight 5b
Finished --------> /
[ChangeCipherSpec] \ Flight 6
<-------- Finished /
*/
type flightVal uint8
const (
flight0 flightVal = iota + 1
flight1
flight2
flight3
flight4
flight4b
flight5
flight5b
flight6
)
func (f flightVal) String() string { //nolint:cyclop
switch f {
case flight0:
return "Flight 0"
case flight1:
return "Flight 1"
case flight2:
return "Flight 2"
case flight3:
return "Flight 3"
case flight4:
return "Flight 4"
case flight4b:
return "Flight 4b"
case flight5:
return "Flight 5"
case flight5b:
return "Flight 5b"
case flight6:
return "Flight 6"
default:
return "Invalid Flight"
}
}
func (f flightVal) isLastSendFlight() bool {
return f == flight6 || f == flight5b
}
func (f flightVal) isLastRecvFlight() bool {
return f == flight5 || f == flight4b
}

189
vendor/github.com/pion/dtls/v3/flight0handler.go generated vendored Normal file
View file

@ -0,0 +1,189 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"crypto/rand"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
)
// renegotiationInfoSCSV is TLS_EMPTY_RENEGOTIATION_INFO_SCSV defined in RFC 5746.
// https://datatracker.ietf.org/doc/html/rfc5746#section-3.3.
const renegotiationInfoSCSV uint16 = 0x00ff
//nolint:cyclop,gocognit
func flight0Parse(
_ context.Context,
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
seq, msgs, ok := cache.fullPullMap(0, state.cipherSuite,
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
// Connection Identifiers must be negotiated afresh on session resumption.
// https://datatracker.ietf.org/doc/html/rfc9146#name-the-connection_id-extension
state.setLocalConnectionID(nil)
state.remoteConnectionID = nil
state.handshakeRecvSequence = seq
var clientHello *handshake.MessageClientHello
// Validate type
if clientHello, ok = msgs[handshake.TypeClientHello].(*handshake.MessageClientHello); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
if !clientHello.Version.Equal(protocol.Version1_2) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion
}
state.remoteRandom = clientHello.Random
cipherSuites := []CipherSuite{}
for _, id := range clientHello.CipherSuiteIDs {
if id == renegotiationInfoSCSV {
state.remoteSupportsRenegotiation = true
continue
}
if c := cipherSuiteForID(CipherSuiteID(id), cfg.customCipherSuites); c != nil {
cipherSuites = append(cipherSuites, c)
}
}
if state.cipherSuite, ok = findMatchingCipherSuite(cipherSuites, cfg.localCipherSuites); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errCipherSuiteNoIntersection
}
for _, val := range clientHello.Extensions {
switch ext := val.(type) {
case *extension.SupportedEllipticCurves:
if len(ext.EllipticCurves) == 0 {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoSupportedEllipticCurves
}
state.namedCurve = ext.EllipticCurves[0]
case *extension.UseSRTP:
profile, ok := findMatchingSRTPProfile(cfg.localSRTPProtectionProfiles, ext.ProtectionProfiles)
if !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errServerNoMatchingSRTPProfile
}
state.setSRTPProtectionProfile(profile)
state.remoteSRTPMasterKeyIdentifier = ext.MasterKeyIdentifier
case *extension.UseExtendedMasterSecret:
if cfg.extendedMasterSecret != DisableExtendedMasterSecret {
state.extendedMasterSecret = true
}
case *extension.ServerName:
state.serverName = ext.ServerName // remote server name
case *extension.RenegotiationInfo:
state.remoteSupportsRenegotiation = true
case *extension.ALPN:
state.peerSupportedProtocols = ext.ProtocolNameList
case *extension.ConnectionID:
// Only set connection ID to be sent if server supports connection
// IDs.
if cfg.connectionIDGenerator != nil {
state.remoteConnectionID = ext.CID
}
case *extension.SignatureAlgorithmsCert:
// Store the client's certificate signature schemes for later validation
state.remoteCertSignatureSchemes = ext.SignatureHashAlgorithms
}
}
// If the client doesn't support connection IDs, the server should not
// expect one to be sent.
if state.remoteConnectionID == nil {
state.setLocalConnectionID(nil)
}
if cfg.extendedMasterSecret == RequireExtendedMasterSecret && !state.extendedMasterSecret {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errServerRequiredButNoClientEMS
}
if state.localKeypair == nil {
var err error
state.localKeypair, err = elliptic.GenerateKeypair(state.namedCurve)
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err
}
}
nextFlight := flight2
if cfg.insecureSkipHelloVerify {
nextFlight = flight4
}
return handleHelloResume(clientHello.SessionID, state, cfg, nextFlight)
}
func handleHelloResume(
sessionID []byte,
state *State,
cfg *handshakeConfig,
next flightVal,
) (flightVal, *alert.Alert, error) {
if len(sessionID) > 0 && cfg.sessionStore != nil {
if s, err := cfg.sessionStore.Get(sessionID); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
} else if s.ID != nil {
cfg.log.Tracef("[handshake] resume session: %x", sessionID)
state.SessionID = sessionID
state.masterSecret = s.Secret
if err := state.initCipherSuite(); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
clientRandom := state.localRandom.MarshalFixed()
cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret)
return flight4b, nil, nil
}
}
return next, nil, nil
}
func flight0Generate(
_ flightConn,
state *State,
_ *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
// Initialize
if !cfg.insecureSkipHelloVerify {
state.cookie = make([]byte, cookieLength)
if _, err := rand.Read(state.cookie); err != nil {
return nil, nil, err
}
}
var zeroEpoch uint16
state.localEpoch.Store(zeroEpoch)
state.remoteEpoch.Store(zeroEpoch)
state.namedCurve = defaultNamedCurve
if err := state.localRandom.Populate(); err != nil {
return nil, nil, err
}
return nil, nil, nil
}

189
vendor/github.com/pion/dtls/v3/flight1handler.go generated vendored Normal file
View file

@ -0,0 +1,189 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight1Parse(
ctx context.Context,
conn flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
// HelloVerifyRequest can be skipped by the server,
// so allow ServerHello during flight1 also
seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeHelloVerifyRequest, cfg.initialEpoch, false, true},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, true},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
if _, ok := msgs[handshake.TypeServerHello]; ok {
// Flight1 and flight2 were skipped.
// Parse as flight3.
return flight3Parse(ctx, conn, state, cache, cfg)
}
if h, ok := msgs[handshake.TypeHelloVerifyRequest].(*handshake.MessageHelloVerifyRequest); ok {
// DTLS 1.2 clients must not assume that the server will use the protocol version
// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
if !h.Version.Equal(protocol.Version1_0) && !h.Version.Equal(protocol.Version1_2) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion
}
state.cookie = append([]byte{}, h.Cookie...)
state.handshakeRecvSequence = seq
return flight3, nil, nil
}
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
//nolint:cyclop
func flight1Generate(
conn flightConn,
state *State,
_ *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
var zeroEpoch uint16
state.localEpoch.Store(zeroEpoch)
state.remoteEpoch.Store(zeroEpoch)
state.namedCurve = defaultNamedCurve
state.cookie = nil
if err := state.localRandom.Populate(); err != nil {
return nil, nil, err
}
if cfg.helloRandomBytesGenerator != nil {
state.localRandom.RandomBytes = cfg.helloRandomBytesGenerator()
}
extensions := []extension.Extension{
&extension.SupportedSignatureAlgorithms{
SignatureHashAlgorithms: cfg.localSignatureSchemes,
},
&extension.RenegotiationInfo{
RenegotiatedConnection: 0,
},
}
if len(cfg.localCertSignatureSchemes) > 0 {
extensions = append(extensions, &extension.SignatureAlgorithmsCert{
SignatureHashAlgorithms: cfg.localCertSignatureSchemes,
})
}
var setEllipticCurveCryptographyClientHelloExtensions bool
for _, c := range cfg.localCipherSuites {
if c.ECC() {
setEllipticCurveCryptographyClientHelloExtensions = true
break
}
}
if setEllipticCurveCryptographyClientHelloExtensions {
extensions = append(extensions, []extension.Extension{
&extension.SupportedEllipticCurves{
EllipticCurves: cfg.ellipticCurves,
},
&extension.SupportedPointFormats{
PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed},
},
}...)
}
if len(cfg.localSRTPProtectionProfiles) > 0 {
extensions = append(extensions, &extension.UseSRTP{
ProtectionProfiles: cfg.localSRTPProtectionProfiles,
MasterKeyIdentifier: cfg.localSRTPMasterKeyIdentifier,
})
}
if cfg.extendedMasterSecret == RequestExtendedMasterSecret ||
cfg.extendedMasterSecret == RequireExtendedMasterSecret {
extensions = append(extensions, &extension.UseExtendedMasterSecret{
Supported: true,
})
}
if len(cfg.serverName) > 0 {
extensions = append(extensions, &extension.ServerName{ServerName: cfg.serverName})
}
if len(cfg.supportedProtocols) > 0 {
extensions = append(extensions, &extension.ALPN{ProtocolNameList: cfg.supportedProtocols})
}
if cfg.sessionStore != nil {
cfg.log.Tracef("[handshake] try to resume session")
if s, err := cfg.sessionStore.Get(conn.sessionKey()); err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
} else if s.ID != nil {
cfg.log.Tracef("[handshake] get saved session: %x", s.ID)
state.SessionID = s.ID
state.masterSecret = s.Secret
}
}
// If we have a connection ID generator, use it. The CID may be zero length,
// in which case we are just requesting that the server send us a CID to
// use.
if cfg.connectionIDGenerator != nil {
state.setLocalConnectionID(cfg.connectionIDGenerator())
// The presence of a generator indicates support for connection IDs. We
// use the presence of a non-nil local CID in flight 3 to determine
// whether we send a CID in the second ClientHello, so we convert any
// nil CID returned by a generator to []byte{}.
if state.getLocalConnectionID() == nil {
state.setLocalConnectionID([]byte{})
}
extensions = append(extensions, &extension.ConnectionID{CID: state.getLocalConnectionID()})
}
clientHello := &handshake.MessageClientHello{
Version: protocol.Version1_2,
SessionID: state.SessionID,
Cookie: state.cookie,
Random: state.localRandom,
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
CompressionMethods: defaultCompressionMethods(),
Extensions: extensions,
}
var content handshake.Handshake
if cfg.clientHelloMessageHook != nil {
content = handshake.Handshake{Message: cfg.clientHelloMessageHook(*clientHello)}
} else {
content = handshake.Handshake{Message: clientHello}
}
return []*packet{
{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &content,
},
},
}, nil, nil
}

77
vendor/github.com/pion/dtls/v3/flight2handler.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"bytes"
"context"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight2Parse(
ctx context.Context,
c flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
)
if !ok {
// Client may retransmit the first ClientHello when HelloVerifyRequest is dropped.
// Parse as flight 0 in this case.
return flight0Parse(ctx, c, state, cache, cfg)
}
state.handshakeRecvSequence = seq
var clientHello *handshake.MessageClientHello
// Validate type
if clientHello, ok = msgs[handshake.TypeClientHello].(*handshake.MessageClientHello); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
if !clientHello.Version.Equal(protocol.Version1_2) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion
}
if len(clientHello.Cookie) == 0 {
return 0, nil, nil
}
if !bytes.Equal(state.cookie, clientHello.Cookie) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.AccessDenied}, errCookieMismatch
}
return flight4, nil, nil
}
func flight2Generate(
_ flightConn,
state *State,
_ *handshakeCache,
_ *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
state.handshakeSendSequence = 0
return []*packet{
{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageHelloVerifyRequest{
Version: protocol.Version1_2,
Cookie: state.cookie,
},
},
},
},
}, nil, nil
}

363
vendor/github.com/pion/dtls/v3/flight3handler.go generated vendored Normal file
View file

@ -0,0 +1,363 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"bytes"
"context"
"github.com/pion/dtls/v3/internal/ciphersuite/types"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
//nolint:gocognit,gocyclo,maintidx,cyclop
func flight3Parse(
ctx context.Context,
conn flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
// Clients may receive multiple HelloVerifyRequest messages with different cookies.
// Clients SHOULD handle this by sending a new ClientHello with a cookie in response
// to the new HelloVerifyRequest. RFC 6347 Section 4.2.1
seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeHelloVerifyRequest, cfg.initialEpoch, false, true},
)
if ok {
if h, msgOk := msgs[handshake.TypeHelloVerifyRequest].(*handshake.MessageHelloVerifyRequest); msgOk {
// DTLS 1.2 clients must not assume that the server will use the protocol version
// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
if !h.Version.Equal(protocol.Version1_0) && !h.Version.Equal(protocol.Version1_2) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion
}
state.cookie = append([]byte{}, h.Cookie...)
state.handshakeRecvSequence = seq
return flight3, nil, nil
}
}
_, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
)
if !ok {
// Don't have enough messages. Keep reading
return 0, nil, nil
}
if serverHelloMsg, msgOk := msgs[handshake.TypeServerHello].(*handshake.MessageServerHello); msgOk { //nolint:nestif
if !serverHelloMsg.Version.Equal(protocol.Version1_2) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion
}
for _, v := range serverHelloMsg.Extensions {
switch ext := v.(type) {
case *extension.UseSRTP:
profile, found := findMatchingSRTPProfile(ext.ProtectionProfiles, cfg.localSRTPProtectionProfiles)
if !found {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, errClientNoMatchingSRTPProfile
}
state.setSRTPProtectionProfile(profile)
state.remoteSRTPMasterKeyIdentifier = ext.MasterKeyIdentifier
case *extension.UseExtendedMasterSecret:
if cfg.extendedMasterSecret != DisableExtendedMasterSecret {
state.extendedMasterSecret = true
}
case *extension.ALPN:
if len(ext.ProtocolNameList) > 1 { // This should be exactly 1, the zero case is handle when unmarshalling
return 0, &alert.Alert{
Level: alert.Fatal,
Description: alert.InternalError,
}, extension.ErrALPNInvalidFormat // Meh, internal error?
}
state.NegotiatedProtocol = ext.ProtocolNameList[0]
case *extension.ConnectionID:
// Only set connection ID to be sent if client supports connection
// IDs.
if cfg.connectionIDGenerator != nil {
state.remoteConnectionID = ext.CID
}
}
}
// If the server doesn't support connection IDs, the client should not
// expect one to be sent.
if state.remoteConnectionID == nil {
state.setLocalConnectionID(nil)
}
if cfg.extendedMasterSecret == RequireExtendedMasterSecret && !state.extendedMasterSecret {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errClientRequiredButNoServerEMS
}
if len(cfg.localSRTPProtectionProfiles) > 0 && state.getSRTPProtectionProfile() == 0 {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errRequestedButNoSRTPExtension
}
remoteCipherSuite := cipherSuiteForID(CipherSuiteID(*serverHelloMsg.CipherSuiteID), cfg.customCipherSuites)
if remoteCipherSuite == nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errCipherSuiteNoIntersection
}
selectedCipherSuite, found := findMatchingCipherSuite([]CipherSuite{remoteCipherSuite}, cfg.localCipherSuites)
if !found {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errInvalidCipherSuite
}
state.cipherSuite = selectedCipherSuite
state.remoteRandom = serverHelloMsg.Random
cfg.log.Tracef("[handshake] use cipher suite: %s", selectedCipherSuite.String())
if len(serverHelloMsg.SessionID) > 0 && bytes.Equal(state.SessionID, serverHelloMsg.SessionID) {
return handleResumption(ctx, conn, state, cache, cfg)
}
if len(state.SessionID) > 0 {
cfg.log.Tracef("[handshake] clean old session : %s", state.SessionID)
if err := cfg.sessionStore.Del(state.SessionID); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
if cfg.sessionStore == nil {
state.SessionID = []byte{}
} else {
state.SessionID = serverHelloMsg.SessionID
}
state.masterSecret = []byte{}
}
if cfg.localPSKCallback != nil {
seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence+1, state.cipherSuite,
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, true},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
)
} else {
seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence+1, state.cipherSuite,
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, true},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, true},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
)
}
if !ok {
// Don't have enough messages. Keep reading
return 0, nil, nil
}
state.handshakeRecvSequence = seq
if h, ok := msgs[handshake.TypeCertificate].(*handshake.MessageCertificate); ok {
state.PeerCertificates = h.Certificate
} else if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errInvalidCertificate
}
if h, ok := msgs[handshake.TypeServerKeyExchange].(*handshake.MessageServerKeyExchange); ok {
alertPtr, err := handleServerKeyExchange(conn, state, cfg, h)
if err != nil {
return 0, alertPtr, err
}
}
if creq, ok := msgs[handshake.TypeCertificateRequest].(*handshake.MessageCertificateRequest); ok {
state.remoteCertRequestAlgs = creq.SignatureHashAlgorithms
state.remoteRequestedCertificate = true
}
return flight5, nil, nil
}
func handleResumption(
ctx context.Context,
c flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
if err := state.initCipherSuite(); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
// Now, encrypted packets can be handled
if err := c.handleQueuedPackets(ctx); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence+1, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
var finished *handshake.MessageFinished
if finished, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
)
expectedVerifyData, err := prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if !bytes.Equal(expectedVerifyData, finished.VerifyData) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errVerifyDataMismatch
}
clientRandom := state.localRandom.MarshalFixed()
cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret)
return flight5b, nil, nil
}
//nolint:cyclop
func handleServerKeyExchange(
_ flightConn,
state *State,
cfg *handshakeConfig,
keyExchangeMessage *handshake.MessageServerKeyExchange,
) (*alert.Alert, error) {
var err error
if state.cipherSuite == nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errInvalidCipherSuite
}
if cfg.localPSKCallback != nil { //nolint:nestif
var psk []byte
if psk, err = cfg.localPSKCallback(keyExchangeMessage.IdentityHint); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.IdentityHint = keyExchangeMessage.IdentityHint
switch state.cipherSuite.KeyExchangeAlgorithm() {
case types.KeyExchangeAlgorithmPsk:
state.preMasterSecret = prf.PSKPreMasterSecret(psk)
case (types.KeyExchangeAlgorithmEcdhe | types.KeyExchangeAlgorithmPsk):
if state.localKeypair, err = elliptic.GenerateKeypair(keyExchangeMessage.NamedCurve); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.preMasterSecret, err = prf.EcdhePSKPreMasterSecret(
psk,
keyExchangeMessage.PublicKey,
state.localKeypair.PrivateKey,
state.localKeypair.Curve,
)
if err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
default:
return &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errInvalidCipherSuite
}
} else {
if state.localKeypair, err = elliptic.GenerateKeypair(keyExchangeMessage.NamedCurve); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if state.preMasterSecret, err = prf.PreMasterSecret(
keyExchangeMessage.PublicKey,
state.localKeypair.PrivateKey,
state.localKeypair.Curve,
); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
return nil, nil //nolint:nilnil
}
func flight3Generate(
_ flightConn,
state *State,
_ *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
extensions := []extension.Extension{
&extension.SupportedSignatureAlgorithms{
SignatureHashAlgorithms: cfg.localSignatureSchemes,
},
&extension.RenegotiationInfo{
RenegotiatedConnection: 0,
},
}
if len(cfg.localCertSignatureSchemes) > 0 {
extensions = append(extensions, &extension.SignatureAlgorithmsCert{
SignatureHashAlgorithms: cfg.localCertSignatureSchemes,
})
}
if state.namedCurve != 0 {
extensions = append(extensions, []extension.Extension{
&extension.SupportedEllipticCurves{
EllipticCurves: cfg.ellipticCurves,
},
&extension.SupportedPointFormats{
PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed},
},
}...)
}
if len(cfg.localSRTPProtectionProfiles) > 0 {
extensions = append(extensions, &extension.UseSRTP{
ProtectionProfiles: cfg.localSRTPProtectionProfiles,
})
}
if cfg.extendedMasterSecret == RequestExtendedMasterSecret ||
cfg.extendedMasterSecret == RequireExtendedMasterSecret {
extensions = append(extensions, &extension.UseExtendedMasterSecret{
Supported: true,
})
}
if len(cfg.serverName) > 0 {
extensions = append(extensions, &extension.ServerName{ServerName: cfg.serverName})
}
if len(cfg.supportedProtocols) > 0 {
extensions = append(extensions, &extension.ALPN{ProtocolNameList: cfg.supportedProtocols})
}
// If we sent a connection ID on the first ClientHello, send it on the
// second.
if state.getLocalConnectionID() != nil {
extensions = append(extensions, &extension.ConnectionID{CID: state.getLocalConnectionID()})
}
clientHello := &handshake.MessageClientHello{
Version: protocol.Version1_2,
SessionID: state.SessionID,
Cookie: state.cookie,
Random: state.localRandom,
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
CompressionMethods: defaultCompressionMethods(),
Extensions: extensions,
}
var content handshake.Handshake
if cfg.clientHelloMessageHook != nil {
content = handshake.Handshake{Message: cfg.clientHelloMessageHook(*clientHello)}
} else {
content = handshake.Handshake{Message: clientHello}
}
return []*packet{
{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &content,
},
},
}, nil, nil
}

163
vendor/github.com/pion/dtls/v3/flight4bhandler.go generated vendored Normal file
View file

@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"bytes"
"context"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight4bParse(
_ context.Context,
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
var finished *handshake.MessageFinished
if finished, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false},
)
expectedVerifyData, err := prf.VerifyDataClient(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if !bytes.Equal(expectedVerifyData, finished.VerifyData) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errVerifyDataMismatch
}
// Other party may re-transmit the last flight. Keep state to be flight4b.
return flight4b, nil, nil
}
//nolint:cyclop
func flight4bGenerate(
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
var pkts []*packet
extensions := []extension.Extension{&extension.RenegotiationInfo{
RenegotiatedConnection: 0,
}}
if (cfg.extendedMasterSecret == RequestExtendedMasterSecret ||
cfg.extendedMasterSecret == RequireExtendedMasterSecret) && state.extendedMasterSecret {
extensions = append(extensions, &extension.UseExtendedMasterSecret{
Supported: true,
})
}
if state.getSRTPProtectionProfile() != 0 {
extensions = append(extensions, &extension.UseSRTP{
ProtectionProfiles: []SRTPProtectionProfile{state.getSRTPProtectionProfile()},
MasterKeyIdentifier: cfg.localSRTPMasterKeyIdentifier,
})
}
selectedProto, err := extension.ALPNProtocolSelection(cfg.supportedProtocols, state.peerSupportedProtocols)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.NoApplicationProtocol}, err
}
if selectedProto != "" {
extensions = append(extensions, &extension.ALPN{
ProtocolNameList: []string{selectedProto},
})
state.NegotiatedProtocol = selectedProto
}
cipherSuiteID := uint16(state.cipherSuite.ID())
var serverHello handshake.Handshake
serverHelloMessage := &handshake.MessageServerHello{
Version: protocol.Version1_2,
Random: state.localRandom,
SessionID: state.SessionID,
CipherSuiteID: &cipherSuiteID,
CompressionMethod: defaultCompressionMethods()[0],
Extensions: extensions,
}
if cfg.serverHelloMessageHook != nil {
serverHello = handshake.Handshake{Message: cfg.serverHelloMessageHook(*serverHelloMessage)}
} else {
serverHello = handshake.Handshake{Message: serverHelloMessage}
}
serverHello.Header.MessageSequence = uint16(state.handshakeSendSequence) //nolint:gosec // G115
if len(state.localVerifyData) == 0 {
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
)
raw, err := serverHello.Marshal()
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
plainText = append(plainText, raw...)
state.localVerifyData, err = prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &serverHello,
},
},
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &protocol.ChangeCipherSpec{},
},
},
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
Epoch: 1,
},
Content: &handshake.Handshake{
Message: &handshake.MessageFinished{
VerifyData: state.localVerifyData,
},
},
},
shouldEncrypt: true,
resetLocalSequenceNumber: true,
},
)
return pkts, nil, nil
}

500
vendor/github.com/pion/dtls/v3/flight4handler.go generated vendored Normal file
View file

@ -0,0 +1,500 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"crypto"
"crypto/rand"
"crypto/x509"
"github.com/pion/dtls/v3/internal/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/extension"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
//nolint:gocognit,gocyclo,lll,cyclop,maintidx
func flight4Parse(
ctx context.Context,
conn flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, true},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, true},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
// Validate type
var clientKeyExchange *handshake.MessageClientKeyExchange
if clientKeyExchange, ok = msgs[handshake.TypeClientKeyExchange].(*handshake.MessageClientKeyExchange); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
if h, hasCert := msgs[handshake.TypeCertificate].(*handshake.MessageCertificate); hasCert {
state.PeerCertificates = h.Certificate
// If the client offer its certificate, just disable session resumption.
// Otherwise, we have to store the certificate identitfication and expire time.
// And we have to check whether this certificate expired, revoked or changed.
//
// https://curl.se/docs/CVE-2016-5419.html
state.SessionID = nil
}
//nolint:nestif
if verify, hasVerify := msgs[handshake.TypeCertificateVerify].(*handshake.MessageCertificateVerify); hasVerify {
if state.PeerCertificates == nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errCertificateVerifyNoCertificate
}
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
)
// Verify that the pair of hash algorithm and signiture is listed.
var validSignatureScheme bool
for _, ss := range cfg.localSignatureSchemes {
if ss.Hash == verify.HashAlgorithm && ss.Signature == verify.SignatureAlgorithm {
validSignatureScheme = true
break
}
}
if !validSignatureScheme {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes
}
if err := verifyCertificateVerify(
plainText,
verify.HashAlgorithm,
verify.SignatureAlgorithm,
verify.Signature,
state.PeerCertificates,
); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
var chains [][]*x509.Certificate
var err error
var verified bool
if cfg.clientAuth >= VerifyClientCertIfGiven {
// Use cert-specific algorithms if present, otherwise fall back to signature_algorithms per RFC 8446
certAlgs := cfg.localCertSignatureSchemes
if len(certAlgs) == 0 {
certAlgs = cfg.localSignatureSchemes
}
if chains, err = verifyClientCert(state.PeerCertificates, cfg.clientCAs, certAlgs); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
verified = true
}
if cfg.verifyPeerCertificate != nil {
if err := cfg.verifyPeerCertificate(state.PeerCertificates, chains); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
state.peerCertificatesVerified = verified
} else if state.PeerCertificates != nil {
// A certificate was received, but we haven't seen a CertificateVerify
// keep reading until we receive one
return 0, nil, nil
}
if !state.cipherSuite.IsInitialized() { //nolint:nestif
serverRandom := state.localRandom.MarshalFixed()
clientRandom := state.remoteRandom.MarshalFixed()
var err error
var preMasterSecret []byte
if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypePreSharedKey {
var psk []byte
if psk, err = cfg.localPSKCallback(clientKeyExchange.IdentityHint); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.IdentityHint = clientKeyExchange.IdentityHint
switch state.cipherSuite.KeyExchangeAlgorithm() {
case CipherSuiteKeyExchangeAlgorithmPsk:
preMasterSecret = prf.PSKPreMasterSecret(psk)
case (CipherSuiteKeyExchangeAlgorithmPsk | CipherSuiteKeyExchangeAlgorithmEcdhe):
if preMasterSecret, err = prf.EcdhePSKPreMasterSecret(
psk,
clientKeyExchange.PublicKey,
state.localKeypair.PrivateKey,
state.localKeypair.Curve,
); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
default:
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidCipherSuite
}
} else {
preMasterSecret, err = prf.PreMasterSecret(
clientKeyExchange.PublicKey,
state.localKeypair.PrivateKey,
state.localKeypair.Curve,
)
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err
}
}
if state.extendedMasterSecret {
var sessionHash []byte
sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch)
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.masterSecret, err = prf.ExtendedMasterSecret(preMasterSecret, sessionHash, state.cipherSuite.HashFunc())
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
} else {
state.masterSecret, err = prf.MasterSecret(
preMasterSecret,
clientRandom[:],
serverRandom[:],
state.cipherSuite.HashFunc(),
)
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
if err := state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], false); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret)
}
if len(state.SessionID) > 0 {
s := Session{
ID: state.SessionID,
Secret: state.masterSecret,
}
cfg.log.Tracef("[handshake] save new session: %x", s.ID)
if err := cfg.sessionStore.Set(state.SessionID, s); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
// Now, encrypted packets can be handled
if err := conn.handleQueuedPackets(ctx); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
seq, msgs, ok = cache.fullPullMap(seq, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
state.handshakeRecvSequence = seq
if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous { //nolint:nestif
if cfg.verifyConnection != nil {
stateClone, err := state.clone()
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if err := cfg.verifyConnection(stateClone); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
return flight6, nil, nil
}
switch cfg.clientAuth {
case RequireAnyClientCert:
if state.PeerCertificates == nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired
}
case VerifyClientCertIfGiven:
if state.PeerCertificates != nil && !state.peerCertificatesVerified {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified
}
case RequireAndVerifyClientCert:
if state.PeerCertificates == nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired
}
if !state.peerCertificatesVerified {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified
}
case NoClientCert, RequestClientCert:
// go to flight6
}
if cfg.verifyConnection != nil {
stateClone, err := state.clone()
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if err := cfg.verifyConnection(stateClone); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
return flight6, nil, nil
}
//nolint:gocognit,cyclop,maintidx
func flight4Generate(
_ flightConn,
state *State,
_ *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
extensions := []extension.Extension{}
if (cfg.extendedMasterSecret == RequestExtendedMasterSecret ||
cfg.extendedMasterSecret == RequireExtendedMasterSecret) && state.extendedMasterSecret {
extensions = append(extensions, &extension.UseExtendedMasterSecret{
Supported: true,
})
}
if state.getSRTPProtectionProfile() != 0 {
extensions = append(extensions, &extension.UseSRTP{
ProtectionProfiles: []SRTPProtectionProfile{state.getSRTPProtectionProfile()},
MasterKeyIdentifier: cfg.localSRTPMasterKeyIdentifier,
})
}
if state.remoteSupportsRenegotiation {
extensions = append(extensions, &extension.RenegotiationInfo{
RenegotiatedConnection: 0,
})
}
if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate {
extensions = append(extensions, &extension.SupportedPointFormats{
PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed},
})
}
selectedProto, err := extension.ALPNProtocolSelection(cfg.supportedProtocols, state.peerSupportedProtocols)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.NoApplicationProtocol}, err
}
if selectedProto != "" {
extensions = append(extensions, &extension.ALPN{
ProtocolNameList: []string{selectedProto},
})
state.NegotiatedProtocol = selectedProto
}
// If we have a connection ID generator, we are willing to use connection
// IDs. We already know whether the client supports connection IDs from
// parsing the ClientHello, so avoid setting local connection ID if the
// client won't send it.
if cfg.connectionIDGenerator != nil && state.remoteConnectionID != nil {
state.setLocalConnectionID(cfg.connectionIDGenerator())
extensions = append(extensions, &extension.ConnectionID{CID: state.getLocalConnectionID()})
}
var pkts []*packet
cipherSuiteID := uint16(state.cipherSuite.ID())
if cfg.sessionStore != nil {
state.SessionID = make([]byte, sessionLength)
if _, err := rand.Read(state.SessionID); err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
serverHello := &handshake.MessageServerHello{
Version: protocol.Version1_2,
Random: state.localRandom,
SessionID: state.SessionID,
CipherSuiteID: &cipherSuiteID,
CompressionMethod: defaultCompressionMethods()[0],
Extensions: extensions,
}
var content handshake.Handshake
if cfg.serverHelloMessageHook != nil {
content = handshake.Handshake{Message: cfg.serverHelloMessageHook(*serverHello)}
} else {
content = handshake.Handshake{Message: serverHello}
}
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &content,
},
})
switch {
case state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate:
certificate, err := cfg.getCertificate(&ClientHelloInfo{
ServerName: state.serverName,
CipherSuites: []ciphersuite.ID{state.cipherSuite.ID()},
RandomBytes: state.remoteRandom.RandomBytes,
})
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err
}
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageCertificate{
Certificate: certificate.Certificate,
},
},
},
})
serverRandom := state.localRandom.MarshalFixed()
clientRandom := state.remoteRandom.MarshalFixed()
signer, ok := certificate.PrivateKey.(crypto.Signer)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidPrivateKey
}
// Find compatible signature scheme
signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, signer)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err
}
signature, err := generateKeySignature(
clientRandom[:],
serverRandom[:],
state.localKeypair.PublicKey,
state.namedCurve,
signer,
signatureHashAlgo.Hash,
signatureHashAlgo.Signature,
)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.localKeySignature = signature
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageServerKeyExchange{
EllipticCurveType: elliptic.CurveTypeNamedCurve,
NamedCurve: state.namedCurve,
PublicKey: state.localKeypair.PublicKey,
HashAlgorithm: signatureHashAlgo.Hash,
SignatureAlgorithm: signatureHashAlgo.Signature,
Signature: state.localKeySignature,
},
},
},
})
if cfg.clientAuth > NoClientCert {
// An empty list of certificateAuthorities signals to
// the client that it may send any certificate in response
// to our request. When we know the CAs we trust, then
// we can send them down, so that the client can choose
// an appropriate certificate to give to us.
var certificateAuthorities [][]byte
if cfg.clientCAs != nil {
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR
// because cert does not come from SystemCertPool and it's ok if certificate
// authorities is empty.
certificateAuthorities = cfg.clientCAs.Subjects()
}
certReq := &handshake.MessageCertificateRequest{
CertificateTypes: []clientcertificate.Type{clientcertificate.RSASign, clientcertificate.ECDSASign},
SignatureHashAlgorithms: cfg.localSignatureSchemes,
CertificateAuthoritiesNames: certificateAuthorities,
}
var content handshake.Handshake
if cfg.certificateRequestMessageHook != nil {
content = handshake.Handshake{Message: cfg.certificateRequestMessageHook(*certReq)}
} else {
content = handshake.Handshake{Message: certReq}
}
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &content,
},
})
}
case cfg.localPSKIdentityHint != nil ||
state.cipherSuite.KeyExchangeAlgorithm().Has(CipherSuiteKeyExchangeAlgorithmEcdhe):
// To help the client in selecting which identity to use, the server
// can provide a "PSK identity hint" in the ServerKeyExchange message.
// If no hint is provided and cipher suite doesn't use elliptic curve,
// the ServerKeyExchange message is omitted.
//
// https://tools.ietf.org/html/rfc4279#section-2
srvExchange := &handshake.MessageServerKeyExchange{
IdentityHint: cfg.localPSKIdentityHint,
}
if state.cipherSuite.KeyExchangeAlgorithm().Has(CipherSuiteKeyExchangeAlgorithmEcdhe) {
srvExchange.EllipticCurveType = elliptic.CurveTypeNamedCurve
srvExchange.NamedCurve = state.namedCurve
srvExchange.PublicKey = state.localKeypair.PublicKey
}
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: srvExchange,
},
},
})
}
pkts = append(pkts, &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageServerHelloDone{},
},
},
})
return pkts, nil, nil
}

89
vendor/github.com/pion/dtls/v3/flight5bhandler.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight5bParse(
_ context.Context,
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-1, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
// Other party may re-transmit the last flight. Keep state to be flight5b.
return flight5b, nil, nil
}
func flight5bGenerate(
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) { //nolint:gocognit
var pkts []*packet
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &protocol.ChangeCipherSpec{},
},
})
if len(state.localVerifyData) == 0 {
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false},
)
var err error
state.localVerifyData, err = prf.VerifyDataClient(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
Epoch: 1,
},
Content: &handshake.Handshake{
Message: &handshake.MessageFinished{
VerifyData: state.localVerifyData,
},
},
},
shouldEncrypt: true,
resetLocalSequenceNumber: true,
})
return pkts, nil, nil
}

410
vendor/github.com/pion/dtls/v3/flight5handler.go generated vendored Normal file
View file

@ -0,0 +1,410 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight5Parse(
_ context.Context,
conn flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
var finished *handshake.MessageFinished
if finished, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
expectedVerifyData, err := prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
if !bytes.Equal(expectedVerifyData, finished.VerifyData) {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errVerifyDataMismatch
}
if len(state.SessionID) > 0 {
s := Session{
ID: state.SessionID,
Secret: state.masterSecret,
}
cfg.log.Tracef("[handshake] save new session: %x", s.ID)
if err := cfg.sessionStore.Set(conn.sessionKey(), s); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
return flight5, nil, nil
}
//nolint:gocognit,cyclop,maintidx
func flight5Generate(
conn flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
var signer crypto.Signer
var pkts []*packet
if state.remoteRequestedCertificate { //nolint:nestif
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-2, state.cipherSuite,
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false})
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errClientCertificateRequired
}
reqInfo := CertificateRequestInfo{}
if r, ok2 := msgs[handshake.TypeCertificateRequest].(*handshake.MessageCertificateRequest); ok2 {
reqInfo.AcceptableCAs = r.CertificateAuthoritiesNames
} else {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errClientCertificateRequired
}
certificate, err := cfg.getClientCertificate(&reqInfo)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err
}
if certificate == nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errNotAcceptableCertificateChain
}
if certificate.Certificate != nil {
signer, ok = certificate.PrivateKey.(crypto.Signer)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errInvalidPrivateKey
}
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageCertificate{
Certificate: certificate.Certificate,
},
},
},
})
}
clientKeyExchange := &handshake.MessageClientKeyExchange{}
if cfg.localPSKCallback == nil {
clientKeyExchange.PublicKey = state.localKeypair.PublicKey
} else {
clientKeyExchange.IdentityHint = cfg.localPSKIdentityHint
}
if state != nil && state.localKeypair != nil && len(state.localKeypair.PublicKey) > 0 {
clientKeyExchange.PublicKey = state.localKeypair.PublicKey
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: clientKeyExchange,
},
},
})
serverKeyExchangeData := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
)
serverKeyExchange := &handshake.MessageServerKeyExchange{}
// handshakeMessageServerKeyExchange is optional for PSK
if len(serverKeyExchangeData) == 0 {
alertPtr, err := handleServerKeyExchange(conn, state, cfg, &handshake.MessageServerKeyExchange{})
if err != nil {
return nil, alertPtr, err
}
} else {
rawHandshake := &handshake.Handshake{
KeyExchangeAlgorithm: state.cipherSuite.KeyExchangeAlgorithm(),
}
err := rawHandshake.Unmarshal(serverKeyExchangeData)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, err
}
switch h := rawHandshake.Message.(type) {
case *handshake.MessageServerKeyExchange:
serverKeyExchange = h
default:
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, errInvalidContentType
}
}
// Append not-yet-sent packets
merged := []byte{}
seqPred := uint16(state.handshakeSendSequence) //nolint:gosec // G115
for _, p := range pkts {
h, ok := p.record.Content.(*handshake.Handshake)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidContentType
}
h.Header.MessageSequence = seqPred
seqPred++
raw, err := h.Marshal()
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
merged = append(merged, raw...)
}
if alertPtr, err := initializeCipherSuite(state, cache, cfg, serverKeyExchange, merged); err != nil {
return nil, alertPtr, err
}
// If the client has sent a certificate with signing ability, a digitally-signed
// CertificateVerify message is sent to explicitly verify possession of the
// private key in the certificate.
if state.remoteRequestedCertificate && signer != nil {
plainText := append(cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
), merged...)
// Find compatible signature scheme
signatureHashAlgo, err := signaturehash.SelectSignatureScheme(state.remoteCertRequestAlgs, signer)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err
}
certVerify, err := generateCertificateVerify(plainText, signer, signatureHashAlgo.Hash, signatureHashAlgo.Signature)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.localCertificatesVerify = certVerify
pkt := &packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &handshake.Handshake{
Message: &handshake.MessageCertificateVerify{
HashAlgorithm: signatureHashAlgo.Hash,
SignatureAlgorithm: signatureHashAlgo.Signature,
Signature: state.localCertificatesVerify,
},
},
},
}
pkts = append(pkts, pkt)
h, ok := pkt.record.Content.(*handshake.Handshake)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidContentType
}
h.Header.MessageSequence = seqPred
// seqPred++ // this is the last use of seqPred
raw, err := h.Marshal()
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
merged = append(merged, raw...)
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &protocol.ChangeCipherSpec{},
},
})
if len(state.localVerifyData) == 0 {
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
var err error
state.localVerifyData, err = prf.VerifyDataClient(
state.masterSecret,
append(plainText, merged...),
state.cipherSuite.HashFunc(),
)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
Epoch: 1,
},
Content: &handshake.Handshake{
Message: &handshake.MessageFinished{
VerifyData: state.localVerifyData,
},
},
},
shouldWrapCID: len(state.remoteConnectionID) > 0,
shouldEncrypt: true,
resetLocalSequenceNumber: true,
})
return pkts, nil, nil
}
//nolint:gocognit,cyclop
func initializeCipherSuite(
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
handshakeKeyExchange *handshake.MessageServerKeyExchange,
sendingPlainText []byte,
) (*alert.Alert, error) {
if state.cipherSuite.IsInitialized() {
return nil, nil //nolint
}
clientRandom := state.localRandom.MarshalFixed()
serverRandom := state.remoteRandom.MarshalFixed()
var err error
if state.extendedMasterSecret {
var sessionHash []byte
sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch, sendingPlainText)
if err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
state.masterSecret, err = prf.ExtendedMasterSecret(state.preMasterSecret, sessionHash, state.cipherSuite.HashFunc())
if err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err
}
} else {
state.masterSecret, err = prf.MasterSecret(
state.preMasterSecret,
clientRandom[:],
serverRandom[:],
state.cipherSuite.HashFunc(),
)
if err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate { //nolint:nestif
// Verify that the pair of hash algorithm and signiture is listed.
var validSignatureScheme bool
for _, ss := range cfg.localSignatureSchemes {
if ss.Hash == handshakeKeyExchange.HashAlgorithm && ss.Signature == handshakeKeyExchange.SignatureAlgorithm {
validSignatureScheme = true
break
}
}
if !validSignatureScheme {
return &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes
}
expectedMsg := valueKeyMessage(
clientRandom[:],
serverRandom[:],
handshakeKeyExchange.PublicKey,
handshakeKeyExchange.NamedCurve,
)
if err = verifyKeySignature(
expectedMsg,
handshakeKeyExchange.Signature,
handshakeKeyExchange.HashAlgorithm,
handshakeKeyExchange.SignatureAlgorithm,
state.PeerCertificates,
); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
var chains [][]*x509.Certificate
if !cfg.insecureSkipVerify {
certAlgs := cfg.localCertSignatureSchemes
if len(certAlgs) == 0 {
certAlgs = cfg.localSignatureSchemes
}
if chains, err = verifyServerCert(state.PeerCertificates, cfg.rootCAs, cfg.serverName, certAlgs); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
if cfg.verifyPeerCertificate != nil {
if err = cfg.verifyPeerCertificate(state.PeerCertificates, chains); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
}
if cfg.verifyConnection != nil {
stateClone, errC := state.clone()
if errC != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errC
}
if errC = cfg.verifyConnection(stateClone); errC != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errC
}
}
if err = state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], true); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret)
return nil, nil //nolint
}

98
vendor/github.com/pion/dtls/v3/flight6handler.go generated vendored Normal file
View file

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
func flight6Parse(
_ context.Context,
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) (flightVal, *alert.Alert, error) {
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-1, state.cipherSuite,
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
if !ok {
// No valid message received. Keep reading
return 0, nil, nil
}
if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
}
// Other party may re-transmit the last flight. Keep state to be flight6.
return flight6, nil, nil
}
func flight6Generate(
_ flightConn,
state *State,
cache *handshakeCache,
cfg *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
var pkts []*packet
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
},
Content: &protocol.ChangeCipherSpec{},
},
})
if len(state.localVerifyData) == 0 {
plainText := cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
)
var err error
state.localVerifyData, err = prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
}
pkts = append(pkts,
&packet{
record: &recordlayer.RecordLayer{
Header: recordlayer.Header{
Version: protocol.Version1_2,
Epoch: 1,
},
Content: &handshake.Handshake{
Message: &handshake.MessageFinished{
VerifyData: state.localVerifyData,
},
},
},
shouldWrapCID: len(state.remoteConnectionID) > 0,
shouldEncrypt: true,
resetLocalSequenceNumber: true,
},
)
return pkts, nil, nil
}

74
vendor/github.com/pion/dtls/v3/flighthandler.go generated vendored Normal file
View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"github.com/pion/dtls/v3/pkg/protocol/alert"
)
// Parse received handshakes and return next flightVal.
type flightParser func(
context.Context,
flightConn,
*State,
*handshakeCache,
*handshakeConfig,
) (flightVal, *alert.Alert, error)
// Generate flights.
type flightGenerator func(flightConn, *State, *handshakeCache, *handshakeConfig) ([]*packet, *alert.Alert, error)
func (f flightVal) getFlightParser() (flightParser, error) { //nolint:cyclop
switch f {
case flight0:
return flight0Parse, nil
case flight1:
return flight1Parse, nil
case flight2:
return flight2Parse, nil
case flight3:
return flight3Parse, nil
case flight4:
return flight4Parse, nil
case flight4b:
return flight4bParse, nil
case flight5:
return flight5Parse, nil
case flight5b:
return flight5bParse, nil
case flight6:
return flight6Parse, nil
default:
return nil, errInvalidFlight
}
}
func (f flightVal) getFlightGenerator() (gen flightGenerator, retransmit bool, err error) { //nolint:cyclop
switch f {
case flight0:
return flight0Generate, true, nil
case flight1:
return flight1Generate, true, nil
case flight2:
// https://tools.ietf.org/html/rfc6347#section-3.2.1
// HelloVerifyRequests must not be retransmitted.
return flight2Generate, false, nil
case flight3:
return flight3Generate, true, nil
case flight4:
return flight4Generate, true, nil
case flight4b:
return flight4bGenerate, true, nil
case flight5:
return flight5Generate, true, nil
case flight5b:
return flight5bGenerate, true, nil
case flight6:
return flight6Generate, true, nil
default:
return nil, false, errInvalidFlight
}
}

151
vendor/github.com/pion/dtls/v3/fragment_buffer.go generated vendored Normal file
View file

@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
const (
// 2 megabytes.
fragmentBufferMaxSize = 2000000
fragmentBufferMaxCount = 1000
)
type fragment struct {
recordLayerHeader recordlayer.Header
handshakeHeader handshake.Header
data []byte
}
type fragments struct {
fragmentByOffset map[uint32]*fragment
fragmentsLength uint32
handshakeLength uint32
}
type fragmentBuffer struct {
// map of MessageSequenceNumbers that hold slices of fragments
cache map[uint16]*fragments
currentMessageSequenceNumber uint16
totalBufferSize int
totalFragmentCount int
}
func newFragmentBuffer() *fragmentBuffer {
return &fragmentBuffer{cache: map[uint16]*fragments{}}
}
// current total size of buffer.
func (f *fragmentBuffer) size() int {
return f.totalBufferSize
}
// Attempts to push a DTLS packet to the fragmentBuffer
// when it returns true it means the fragmentBuffer has inserted and the buffer shouldn't be handled
// when an error returns it is fatal, and the DTLS connection should be stopped.
func (f *fragmentBuffer) push(buf []byte) (isHandshake, isRetransmit bool, err error) { //nolint:cyclop
if f.size()+len(buf) >= fragmentBufferMaxSize || f.totalFragmentCount >= fragmentBufferMaxCount {
return false, false, errFragmentBufferOverflow
}
recordLayerHeader := recordlayer.Header{}
if err := recordLayerHeader.Unmarshal(buf); err != nil {
return false, false, err
}
// fragment isn't a handshake, we don't need to handle it
if recordLayerHeader.ContentType != protocol.ContentTypeHandshake {
return false, false, nil
}
frag := new(fragment)
for buf = buf[recordlayer.FixedHeaderSize:]; len(buf) != 0; frag = new(fragment) { //nolint:gosec // G602
if err := frag.handshakeHeader.Unmarshal(buf); err != nil {
return false, false, err
}
// Fragment is a retransmission. We have already assembled it before successfully
isRetransmit = frag.handshakeHeader.FragmentOffset == 0 &&
frag.handshakeHeader.MessageSequence < f.currentMessageSequenceNumber
end := int(handshake.HeaderLength + frag.handshakeHeader.FragmentLength)
if end > len(buf) {
return false, false, errBufferTooSmall
}
if frag.handshakeHeader.MessageSequence < f.currentMessageSequenceNumber {
buf = buf[end:]
continue
}
messageFragments, ok := f.cache[frag.handshakeHeader.MessageSequence]
if !ok {
messageFragments = &fragments{
fragmentByOffset: map[uint32]*fragment{}, handshakeLength: frag.handshakeHeader.Length,
}
f.cache[frag.handshakeHeader.MessageSequence] = messageFragments
}
// Discard all headers, when rebuilding the packet we will re-build
frag.data = append([]byte{}, buf[handshake.HeaderLength:end]...)
frag.recordLayerHeader = recordLayerHeader
if _, ok = messageFragments.fragmentByOffset[frag.handshakeHeader.FragmentOffset]; !ok {
messageFragments.fragmentByOffset[frag.handshakeHeader.FragmentOffset] = frag
messageFragments.fragmentsLength += frag.handshakeHeader.FragmentLength
f.totalBufferSize += int(frag.handshakeHeader.FragmentLength)
f.totalFragmentCount++
}
buf = buf[end:]
}
return true, isRetransmit, nil
}
func (f *fragmentBuffer) pop() (content []byte, epoch uint16) {
frags, ok := f.cache[f.currentMessageSequenceNumber]
if !ok {
return nil, 0
}
if frags.fragmentsLength != frags.handshakeLength {
return nil, 0
}
var rawMessage []byte
targetOffset := uint32(0)
for i := 0; i < len(frags.fragmentByOffset) && targetOffset < frags.handshakeLength; i++ {
if frag, ok := frags.fragmentByOffset[targetOffset]; ok {
rawMessage = append(rawMessage, frag.data...)
targetOffset = frag.handshakeHeader.FragmentOffset + frag.handshakeHeader.FragmentLength
} else {
return nil, 0
}
}
if int(frags.handshakeLength) != len(rawMessage) {
return nil, 0
}
firstHeader := frags.fragmentByOffset[0].handshakeHeader
firstHeader.FragmentOffset = 0
firstHeader.FragmentLength = firstHeader.Length
rawHeader, _ := firstHeader.Marshal()
messageEpoch := frags.fragmentByOffset[0].recordLayerHeader.Epoch
f.totalBufferSize -= int(frags.fragmentsLength)
f.totalFragmentCount -= len(frags.fragmentByOffset)
delete(f.cache, f.currentMessageSequenceNumber)
f.currentMessageSequenceNumber++
return append(rawHeader, rawMessage...), messageEpoch
}

185
vendor/github.com/pion/dtls/v3/handshake_cache.go generated vendored Normal file
View file

@ -0,0 +1,185 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"sync"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
)
type handshakeCacheItem struct {
typ handshake.Type
isClient bool
epoch uint16
messageSequence uint16
data []byte
}
type handshakeCachePullRule struct {
typ handshake.Type
epoch uint16
isClient bool
optional bool
}
type handshakeCache struct {
cache []*handshakeCacheItem
mu sync.Mutex
}
func newHandshakeCache() *handshakeCache {
return &handshakeCache{}
}
func (h *handshakeCache) push(data []byte, epoch, messageSequence uint16, typ handshake.Type, isClient bool) {
h.mu.Lock()
defer h.mu.Unlock()
h.cache = append(h.cache, &handshakeCacheItem{
data: append([]byte{}, data...),
epoch: epoch,
messageSequence: messageSequence,
typ: typ,
isClient: isClient,
})
}
// returns a list handshakes that match the requested rules
// the list will contain null entries for rules that can't be satisfied
// multiple entries may match a rule, but only the last match is returned (ie ClientHello with cookies).
func (h *handshakeCache) pull(rules ...handshakeCachePullRule) []*handshakeCacheItem {
h.mu.Lock()
defer h.mu.Unlock()
out := make([]*handshakeCacheItem, len(rules))
for i, r := range rules {
for _, c := range h.cache {
if c.typ == r.typ && c.isClient == r.isClient && c.epoch == r.epoch {
switch {
case out[i] == nil:
out[i] = c
case out[i].messageSequence < c.messageSequence:
out[i] = c
}
}
}
}
return out
}
// fullPullMap pulls all handshakes between rules[0] to rules[len(rules)-1] as map.
//
//nolint:cyclop
func (h *handshakeCache) fullPullMap(
startSeq int,
cipherSuite CipherSuite,
rules ...handshakeCachePullRule,
) (int, map[handshake.Type]handshake.Message, bool) {
h.mu.Lock()
defer h.mu.Unlock()
ci := make(map[handshake.Type]*handshakeCacheItem)
for _, rule := range rules {
var item *handshakeCacheItem
for _, c := range h.cache {
if c.typ == rule.typ && c.isClient == rule.isClient && c.epoch == rule.epoch {
switch {
case item == nil:
item = c
case item.messageSequence < c.messageSequence:
item = c
}
}
}
if !rule.optional && item == nil {
// Missing mandatory message.
return startSeq, nil, false
}
ci[rule.typ] = item
}
out := make(map[handshake.Type]handshake.Message)
seq := startSeq
ok := false
for _, r := range rules {
typ := r.typ
i := ci[typ]
if i == nil {
continue
}
var keyExchangeAlgorithm CipherSuiteKeyExchangeAlgorithm
if cipherSuite != nil {
keyExchangeAlgorithm = cipherSuite.KeyExchangeAlgorithm()
}
rawHandshake := &handshake.Handshake{
KeyExchangeAlgorithm: keyExchangeAlgorithm,
}
if err := rawHandshake.Unmarshal(i.data); err != nil {
return startSeq, nil, false
}
if uint16(seq) != rawHandshake.Header.MessageSequence { //nolint:gosec // G115
// There is a gap. Some messages are not arrived.
return startSeq, nil, false
}
seq++
ok = true
out[typ] = rawHandshake.Message
}
if !ok {
return seq, nil, false
}
return seq, out, true
}
// pullAndMerge calls pull and then merges the results, ignoring any null entries.
func (h *handshakeCache) pullAndMerge(rules ...handshakeCachePullRule) []byte {
merged := []byte{}
for _, p := range h.pull(rules...) {
if p != nil {
merged = append(merged, p.data...)
}
}
return merged
}
// sessionHash returns the session hash for Extended Master Secret support
// https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4
func (h *handshakeCache) sessionHash(hf prf.HashFunc, epoch uint16, additional ...[]byte) ([]byte, error) {
merged := []byte{}
// Order defined by https://tools.ietf.org/html/rfc5246#section-7.3
handshakeBuffer := h.pull(
handshakeCachePullRule{handshake.TypeClientHello, epoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, epoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, epoch, false, false},
handshakeCachePullRule{handshake.TypeServerKeyExchange, epoch, false, false},
handshakeCachePullRule{handshake.TypeCertificateRequest, epoch, false, false},
handshakeCachePullRule{handshake.TypeServerHelloDone, epoch, false, false},
handshakeCachePullRule{handshake.TypeCertificate, epoch, true, false},
handshakeCachePullRule{handshake.TypeClientKeyExchange, epoch, true, false},
)
for _, p := range handshakeBuffer {
if p == nil {
continue
}
merged = append(merged, p.data...)
}
for _, a := range additional {
merged = append(merged, a...)
}
hash := hf()
if _, err := hash.Write(merged); err != nil {
return []byte{}, err
}
return hash.Sum(nil), nil
}

364
vendor/github.com/pion/dtls/v3/handshaker.go generated vendored Normal file
View file

@ -0,0 +1,364 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"sync"
"time"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
"github.com/pion/dtls/v3/pkg/protocol/alert"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/logging"
)
// [RFC6347 Section-4.2.4]
// +-----------+
// +---> | PREPARING | <--------------------+
// | +-----------+ |
// | | |
// | | Buffer next flight |
// | | |
// | \|/ |
// | +-----------+ |
// | | SENDING |<------------------+ | Send
// | +-----------+ | | HelloRequest
// Receive | | | |
// next | | Send flight | | or
// flight | +--------+ | |
// | | | Set retransmit timer | | Receive
// | | \|/ | | HelloRequest
// | | +-----------+ | | Send
// +--)--| WAITING |-------------------+ | ClientHello
// | | +-----------+ Timer expires | |
// | | | | |
// | | +------------------------+ |
// Receive | | Send Read retransmit |
// last | | last |
// flight | | flight |
// | | |
// \|/\|/ |
// +-----------+ |
// | FINISHED | -------------------------------+
// +-----------+
// | /|\
// | |
// +---+
// Read retransmit
// Retransmit last flight
type handshakeState uint8
const (
handshakeErrored handshakeState = iota
handshakePreparing
handshakeSending
handshakeWaiting
handshakeFinished
)
func (s handshakeState) String() string {
switch s {
case handshakeErrored:
return "Errored"
case handshakePreparing:
return "Preparing"
case handshakeSending:
return "Sending"
case handshakeWaiting:
return "Waiting"
case handshakeFinished:
return "Finished"
default:
return "Unknown"
}
}
type handshakeFSM struct {
currentFlight flightVal
flights []*packet
retransmit bool
retransmitInterval time.Duration
state *State
cache *handshakeCache
cfg *handshakeConfig
closed chan struct{}
}
type handshakeConfig struct {
localPSKCallback PSKCallback
localPSKIdentityHint []byte
localCipherSuites []CipherSuite // Available CipherSuites
localSignatureSchemes []signaturehash.Algorithm // Available signature schemes
localCertSignatureSchemes []signaturehash.Algorithm // Available signature schemes for certificates
extendedMasterSecret ExtendedMasterSecretType // Policy for the Extended Master Support extension
localSRTPProtectionProfiles []SRTPProtectionProfile // Available SRTPProtectionProfiles, if empty no SRTP support
localSRTPMasterKeyIdentifier []byte
serverName string
supportedProtocols []string
clientAuth ClientAuthType // If we are a client should we request a client certificate
localCertificates []tls.Certificate
nameToCertificate map[string]*tls.Certificate
insecureSkipVerify bool
verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
verifyConnection func(*State) error
sessionStore SessionStore
rootCAs *x509.CertPool
clientCAs *x509.CertPool
initialRetransmitInterval time.Duration
disableRetransmitBackoff bool
customCipherSuites func() []CipherSuite
ellipticCurves []elliptic.Curve
insecureSkipHelloVerify bool
connectionIDGenerator func() []byte
helloRandomBytesGenerator func() [handshake.RandomBytesLength]byte
onFlightState func(flightVal, handshakeState)
log logging.LeveledLogger
keyLogWriter io.Writer
localGetCertificate func(*ClientHelloInfo) (*tls.Certificate, error)
localGetClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error)
initialEpoch uint16
mu sync.Mutex
clientHelloMessageHook func(handshake.MessageClientHello) handshake.Message
serverHelloMessageHook func(handshake.MessageServerHello) handshake.Message
certificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message
resumeState *State
}
type flightConn interface {
notify(ctx context.Context, level alert.Level, desc alert.Description) error
writePackets(context.Context, []*packet) error
recvHandshake() <-chan recvHandshakeState
setLocalEpoch(epoch uint16)
handleQueuedPackets(context.Context) error
sessionKey() []byte
}
func (c *handshakeConfig) writeKeyLog(label string, clientRandom, secret []byte) {
if c.keyLogWriter == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
_, err := fmt.Fprintf(c.keyLogWriter, "%s %x %x\n", label, clientRandom, secret)
if err != nil {
c.log.Debugf("failed to write key log file: %s", err)
}
}
func srvCliStr(isClient bool) string {
if isClient {
return "client"
}
return "server"
}
func newHandshakeFSM(
s *State, cache *handshakeCache, cfg *handshakeConfig,
initialFlight flightVal,
) *handshakeFSM {
return &handshakeFSM{
currentFlight: initialFlight,
state: s,
cache: cache,
cfg: cfg,
retransmitInterval: cfg.initialRetransmitInterval,
closed: make(chan struct{}),
}
}
func (s *handshakeFSM) Run(ctx context.Context, conn flightConn, initialState handshakeState) error {
state := initialState
defer func() {
close(s.closed)
}()
for {
s.cfg.log.Tracef("[handshake:%s] %s: %s", srvCliStr(s.state.isClient), s.currentFlight.String(), state.String())
if s.cfg.onFlightState != nil {
s.cfg.onFlightState(s.currentFlight, state)
}
var err error
switch state {
case handshakePreparing:
state, err = s.prepare(ctx, conn)
case handshakeSending:
state, err = s.send(ctx, conn)
case handshakeWaiting:
state, err = s.wait(ctx, conn)
case handshakeFinished:
state, err = s.finish(ctx, conn)
default:
return errInvalidFSMTransition
}
if err != nil {
return err
}
}
}
func (s *handshakeFSM) Done() <-chan struct{} {
return s.closed
}
func (s *handshakeFSM) prepare(ctx context.Context, conn flightConn) (handshakeState, error) {
s.flights = nil
// Prepare flights
var (
dtlsAlert *alert.Alert
err error
pkts []*packet
)
gen, retransmit, errFlight := s.currentFlight.getFlightGenerator()
if errFlight != nil {
err = errFlight
dtlsAlert = &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}
} else {
pkts, dtlsAlert, err = gen(conn, s.state, s.cache, s.cfg)
s.retransmit = retransmit
}
if dtlsAlert != nil {
if alertErr := conn.notify(ctx, dtlsAlert.Level, dtlsAlert.Description); alertErr != nil {
if err != nil {
err = alertErr
}
}
}
if err != nil {
return handshakeErrored, err
}
s.flights = pkts
epoch := s.cfg.initialEpoch
nextEpoch := epoch
for _, p := range s.flights {
p.record.Header.Epoch += epoch
if p.record.Header.Epoch > nextEpoch {
nextEpoch = p.record.Header.Epoch
}
if h, ok := p.record.Content.(*handshake.Handshake); ok {
h.Header.MessageSequence = uint16(s.state.handshakeSendSequence) //nolint:gosec // G115
s.state.handshakeSendSequence++
}
}
if epoch != nextEpoch {
s.cfg.log.Tracef("[handshake:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.isClient), nextEpoch)
conn.setLocalEpoch(nextEpoch)
}
return handshakeSending, nil
}
func (s *handshakeFSM) send(ctx context.Context, c flightConn) (handshakeState, error) {
// Send flights
if err := c.writePackets(ctx, s.flights); err != nil {
return handshakeErrored, err
}
if s.currentFlight.isLastSendFlight() {
return handshakeFinished, nil
}
return handshakeWaiting, nil
}
func (s *handshakeFSM) wait(ctx context.Context, conn flightConn) (handshakeState, error) { //nolint:gocognit,cyclop
parse, errFlight := s.currentFlight.getFlightParser()
if errFlight != nil {
if alertErr := conn.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil {
return handshakeErrored, alertErr
}
return handshakeErrored, errFlight
}
retransmitTimer := time.NewTimer(s.retransmitInterval)
for {
select {
case state := <-conn.recvHandshake():
if state.isRetransmit {
close(state.done)
// ignore incoming retransmit hints, only rely on the timer-driven path below
// https://github.com/pion/dtls/issues/758
continue
}
nextFlight, alert, err := parse(ctx, conn, s.state, s.cache, s.cfg)
s.retransmitInterval = s.cfg.initialRetransmitInterval
close(state.done)
if alert != nil {
if alertErr := conn.notify(ctx, alert.Level, alert.Description); alertErr != nil {
if err != nil {
err = alertErr
}
}
}
if err != nil {
return handshakeErrored, err
}
if nextFlight == 0 {
break
}
s.cfg.log.Tracef(
"[handshake:%s] %s -> %s",
srvCliStr(s.state.isClient),
s.currentFlight.String(),
nextFlight.String(),
)
if nextFlight.isLastRecvFlight() && s.currentFlight == nextFlight {
return handshakeFinished, nil
}
s.currentFlight = nextFlight
return handshakePreparing, nil
case <-retransmitTimer.C:
if !s.retransmit {
return handshakeWaiting, nil
}
// RFC 4347 4.2.4.1:
// Implementations SHOULD use an initial timer value of 1 second (the minimum defined in RFC 2988 [RFC2988])
// and double the value at each retransmission, up to no less than the RFC 2988 maximum of 60 seconds.
if !s.cfg.disableRetransmitBackoff {
s.retransmitInterval *= 2
}
if s.retransmitInterval > time.Second*60 {
s.retransmitInterval = time.Second * 60
}
return handshakeSending, nil
case <-ctx.Done():
s.retransmitInterval = s.cfg.initialRetransmitInterval
return handshakeErrored, ctx.Err()
}
}
}
func (s *handshakeFSM) finish(ctx context.Context, c flightConn) (handshakeState, error) {
select {
case state := <-c.recvHandshake():
close(state.done)
if s.state.isClient {
return handshakeFinished, nil
} else {
return handshakeSending, nil
}
case <-ctx.Done():
return handshakeErrored, ctx.Err()
}
}

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// Aes128Ccm is a base class used by multiple AES-CCM Ciphers.
type Aes128Ccm struct {
AesCcm
}
func newAes128Ccm(
clientCertificateType clientcertificate.Type,
id ID,
psk bool,
cryptoCCMTagLen ciphersuite.CCMTagLen,
keyExchangeAlgorithm KeyExchangeAlgorithm,
ecc bool,
) *Aes128Ccm {
return &Aes128Ccm{
AesCcm: AesCcm{
clientCertificateType: clientCertificateType,
id: id,
psk: psk,
cryptoCCMTagLen: cryptoCCMTagLen,
keyExchangeAlgorithm: keyExchangeAlgorithm,
ecc: ecc,
},
}
}
// Init initializes the internal Cipher with keying material.
func (c *Aes128Ccm) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const prfKeyLen = 16
return c.AesCcm.Init(masterSecret, clientRandom, serverRandom, isClient, prfKeyLen)
}

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// Aes256Ccm is a base class used by multiple AES-CCM Ciphers.
type Aes256Ccm struct {
AesCcm
}
func newAes256Ccm(
clientCertificateType clientcertificate.Type,
id ID,
psk bool,
cryptoCCMTagLen ciphersuite.CCMTagLen,
keyExchangeAlgorithm KeyExchangeAlgorithm,
ecc bool,
) *Aes256Ccm {
return &Aes256Ccm{
AesCcm: AesCcm{
clientCertificateType: clientCertificateType,
id: id,
psk: psk,
cryptoCCMTagLen: cryptoCCMTagLen,
keyExchangeAlgorithm: keyExchangeAlgorithm,
ecc: ecc,
},
}
}
// Init initializes the internal Cipher with keying material.
func (c *Aes256Ccm) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const prfKeyLen = 32
return c.AesCcm.Init(masterSecret, clientRandom, serverRandom, isClient, prfKeyLen)
}

View file

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha256"
"fmt"
"hash"
"sync/atomic"
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// AesCcm is a base class used by multiple AES-CCM Ciphers.
type AesCcm struct {
ccm atomic.Value // *cryptoCCM
clientCertificateType clientcertificate.Type
id ID
psk bool
keyExchangeAlgorithm KeyExchangeAlgorithm
cryptoCCMTagLen ciphersuite.CCMTagLen
ecc bool
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *AesCcm) CertificateType() clientcertificate.Type {
return c.clientCertificateType
}
// ID returns the ID of the CipherSuite.
func (c *AesCcm) ID() ID {
return c.id
}
func (c *AesCcm) String() string {
return c.id.String()
}
// ECC uses Elliptic Curve Cryptography.
func (c *AesCcm) ECC() bool {
return c.ecc
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *AesCcm) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return c.keyExchangeAlgorithm
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *AesCcm) HashFunc() func() hash.Hash {
return sha256.New
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *AesCcm) AuthenticationType() AuthenticationType {
if c.psk {
return AuthenticationTypePreSharedKey
}
return AuthenticationTypeCertificate
}
// IsInitialized returns if the CipherSuite has keying material and can
// encrypt/decrypt packets.
func (c *AesCcm) IsInitialized() bool {
return c.ccm.Load() != nil
}
// Init initializes the internal Cipher with keying material.
func (c *AesCcm) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool, prfKeyLen int) error {
const (
prfMacLen = 0
prfIvLen = 4
)
keys, err := prf.GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc(),
)
if err != nil {
return err
}
var ccm *ciphersuite.CCM
if isClient {
ccm, err = ciphersuite.NewCCM(
c.cryptoCCMTagLen, keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV,
)
} else {
ccm, err = ciphersuite.NewCCM(
c.cryptoCCMTagLen, keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV,
)
}
c.ccm.Store(ccm)
return err
}
// Encrypt encrypts a single TLS RecordLayer.
func (c *AesCcm) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
cipherSuite, ok := c.ccm.Load().(*ciphersuite.CCM)
if !ok {
return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
}
return cipherSuite.Encrypt(pkt, raw)
}
// Decrypt decrypts a single TLS RecordLayer.
func (c *AesCcm) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
cipherSuite, ok := c.ccm.Load().(*ciphersuite.CCM)
if !ok {
return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
}
return cipherSuite.Decrypt(h, raw)
}

View file

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package ciphersuite provides TLS Ciphers as registered with the IANA
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
package ciphersuite
import (
"errors"
"fmt"
"github.com/pion/dtls/v3/internal/ciphersuite/types"
"github.com/pion/dtls/v3/pkg/protocol"
)
//nolint:err113
var errCipherSuiteNotInit = &protocol.TemporaryError{Err: errors.New("CipherSuite has not been initialized")}
// ID is an ID for our supported CipherSuites.
type ID uint16
func (i ID) String() string { //nolint:cyclop
switch i {
case TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM"
case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
case TLS_PSK_WITH_AES_128_CCM:
return "TLS_PSK_WITH_AES_128_CCM"
case TLS_PSK_WITH_AES_128_CCM_8:
return "TLS_PSK_WITH_AES_128_CCM_8"
case TLS_PSK_WITH_AES_256_CCM_8:
return "TLS_PSK_WITH_AES_256_CCM_8"
case TLS_PSK_WITH_AES_128_GCM_SHA256:
return "TLS_PSK_WITH_AES_128_GCM_SHA256"
case TLS_PSK_WITH_AES_128_CBC_SHA256:
return "TLS_PSK_WITH_AES_128_CBC_SHA256"
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
case TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
return "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256"
default:
return fmt.Sprintf("unknown(%v)", uint16(i))
}
}
// Supported Cipher Suites.
const (
// AES-128-CCM.
TLS_ECDHE_ECDSA_WITH_AES_128_CCM ID = 0xc0ac // nolint: revive,staticcheck
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 ID = 0xc0ae // nolint: revive,staticcheck
// AES-128-GCM-SHA256.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ID = 0xc02b // nolint: revive,staticcheck
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ID = 0xc02f // nolint: revive,staticcheck
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ID = 0xc02c // nolint: revive,staticcheck
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ID = 0xc030 // nolint: revive,staticcheck
// AES-256-CBC-SHA.
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ID = 0xc00a // nolint: revive,staticcheck
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ID = 0xc014 // nolint: revive,staticcheck
TLS_PSK_WITH_AES_128_CCM ID = 0xc0a4 // nolint: revive,staticcheck
TLS_PSK_WITH_AES_128_CCM_8 ID = 0xc0a8 // nolint: revive,staticcheck
TLS_PSK_WITH_AES_256_CCM_8 ID = 0xc0a9 // nolint: revive,staticcheck
TLS_PSK_WITH_AES_128_GCM_SHA256 ID = 0x00a8 // nolint: revive,staticcheck
TLS_PSK_WITH_AES_128_CBC_SHA256 ID = 0x00ae // nolint: revive,staticcheck
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 ID = 0xC037 // nolint: revive,staticcheck
)
// AuthenticationType controls what authentication method is using during the handshake.
type AuthenticationType = types.AuthenticationType
// AuthenticationType Enums.
const (
AuthenticationTypeCertificate AuthenticationType = types.AuthenticationTypeCertificate
AuthenticationTypePreSharedKey AuthenticationType = types.AuthenticationTypePreSharedKey
AuthenticationTypeAnonymous AuthenticationType = types.AuthenticationTypeAnonymous
)
// KeyExchangeAlgorithm controls what exchange algorithm was chosen.
type KeyExchangeAlgorithm = types.KeyExchangeAlgorithm
// KeyExchangeAlgorithm Bitmask.
const (
KeyExchangeAlgorithmNone KeyExchangeAlgorithm = types.KeyExchangeAlgorithmNone
KeyExchangeAlgorithmPsk KeyExchangeAlgorithm = types.KeyExchangeAlgorithmPsk
KeyExchangeAlgorithmEcdhe KeyExchangeAlgorithm = types.KeyExchangeAlgorithmEcdhe
)

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// NewTLSEcdheEcdsaWithAes128Ccm constructs a TLS_ECDHE_ECDSA_WITH_AES_128_CCM Cipher.
func NewTLSEcdheEcdsaWithAes128Ccm() *Aes128Ccm {
return newAes128Ccm(
clientcertificate.ECDSASign,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
false,
ciphersuite.CCMTagLength,
KeyExchangeAlgorithmEcdhe,
true,
)
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// NewTLSEcdheEcdsaWithAes128Ccm8 creates a new TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuite.
func NewTLSEcdheEcdsaWithAes128Ccm8() *Aes128Ccm {
return newAes128Ccm(
clientcertificate.ECDSASign,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
false,
ciphersuite.CCMTagLength8,
KeyExchangeAlgorithmEcdhe,
true,
)
}

View file

@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha256"
"fmt"
"hash"
"sync/atomic"
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// TLSEcdheEcdsaWithAes128GcmSha256 represents a TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuite.
type TLSEcdheEcdsaWithAes128GcmSha256 struct {
gcm atomic.Value // *cryptoGCM
}
// CertificateType returns what type of certficate this CipherSuite exchanges.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) CertificateType() clientcertificate.Type {
return clientcertificate.ECDSASign
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return KeyExchangeAlgorithmEcdhe
}
// ECC uses Elliptic Curve Cryptography.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) ECC() bool {
return true
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) ID() ID {
return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
}
func (c *TLSEcdheEcdsaWithAes128GcmSha256) String() string {
return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) HashFunc() func() hash.Hash {
return sha256.New
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) AuthenticationType() AuthenticationType {
return AuthenticationTypeCertificate
}
// IsInitialized returns if the CipherSuite has keying material and can
// encrypt/decrypt packets.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) IsInitialized() bool {
return c.gcm.Load() != nil
}
func (c *TLSEcdheEcdsaWithAes128GcmSha256) init(
masterSecret, clientRandom, serverRandom []byte,
isClient bool,
prfMacLen, prfKeyLen, prfIvLen int,
hashFunc func() hash.Hash,
) error {
keys, err := prf.GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, hashFunc,
)
if err != nil {
return err
}
var gcm *ciphersuite.GCM
if isClient {
gcm, err = ciphersuite.NewGCM(keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV)
} else {
gcm, err = ciphersuite.NewGCM(keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV)
}
c.gcm.Store(gcm)
return err
}
// Init initializes the internal Cipher with keying material.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const (
prfMacLen = 0
prfKeyLen = 16
prfIvLen = 4
)
return c.init(masterSecret, clientRandom, serverRandom, isClient, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
}
// Encrypt encrypts a single TLS RecordLayer.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
cipherSuite, ok := c.gcm.Load().(*ciphersuite.GCM)
if !ok {
return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
}
return cipherSuite.Encrypt(pkt, raw)
}
// Decrypt decrypts a single TLS RecordLayer.
func (c *TLSEcdheEcdsaWithAes128GcmSha256) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
cipherSuite, ok := c.gcm.Load().(*ciphersuite.GCM)
if !ok {
return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
}
return cipherSuite.Decrypt(h, raw)
}

View file

@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha1" //nolint: gosec,gci
"crypto/sha256"
"fmt"
"hash"
"sync/atomic"
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// TLSEcdheEcdsaWithAes256CbcSha represents a TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuite.
type TLSEcdheEcdsaWithAes256CbcSha struct {
cbc atomic.Value // *cryptoCBC
}
// CertificateType returns what type of certficate this CipherSuite exchanges.
func (c *TLSEcdheEcdsaWithAes256CbcSha) CertificateType() clientcertificate.Type {
return clientcertificate.ECDSASign
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *TLSEcdheEcdsaWithAes256CbcSha) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return KeyExchangeAlgorithmEcdhe
}
// ECC uses Elliptic Curve Cryptography.
func (c *TLSEcdheEcdsaWithAes256CbcSha) ECC() bool {
return true
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheEcdsaWithAes256CbcSha) ID() ID {
return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
}
func (c *TLSEcdheEcdsaWithAes256CbcSha) String() string {
return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *TLSEcdheEcdsaWithAes256CbcSha) HashFunc() func() hash.Hash {
return sha256.New
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *TLSEcdheEcdsaWithAes256CbcSha) AuthenticationType() AuthenticationType {
return AuthenticationTypeCertificate
}
// IsInitialized returns if the CipherSuite has keying material and can
// encrypt/decrypt packets.
func (c *TLSEcdheEcdsaWithAes256CbcSha) IsInitialized() bool {
return c.cbc.Load() != nil
}
// Init initializes the internal Cipher with keying material.
func (c *TLSEcdheEcdsaWithAes256CbcSha) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const (
prfMacLen = 20
prfKeyLen = 32
prfIvLen = 16
)
keys, err := prf.GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc(),
)
if err != nil {
return err
}
var cbc *ciphersuite.CBC
if isClient {
cbc, err = ciphersuite.NewCBC(
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
sha1.New,
)
} else {
cbc, err = ciphersuite.NewCBC(
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
sha1.New,
)
}
c.cbc.Store(cbc)
return err
}
// Encrypt encrypts a single TLS RecordLayer.
func (c *TLSEcdheEcdsaWithAes256CbcSha) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok {
return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
}
return cipherSuite.Encrypt(pkt, raw)
}
// Decrypt decrypts a single TLS RecordLayer.
func (c *TLSEcdheEcdsaWithAes256CbcSha) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok {
return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
}
return cipherSuite.Decrypt(h, raw)
}

View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha512"
"hash"
)
// TLSEcdheEcdsaWithAes256GcmSha384 represents a TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuite.
type TLSEcdheEcdsaWithAes256GcmSha384 struct {
TLSEcdheEcdsaWithAes128GcmSha256
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheEcdsaWithAes256GcmSha384) ID() ID {
return TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
}
func (c *TLSEcdheEcdsaWithAes256GcmSha384) String() string {
return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *TLSEcdheEcdsaWithAes256GcmSha384) HashFunc() func() hash.Hash {
return sha512.New384
}
// Init initializes the internal Cipher with keying material.
func (c *TLSEcdheEcdsaWithAes256GcmSha384) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const (
prfMacLen = 0
prfKeyLen = 32
prfIvLen = 4
)
return c.init(masterSecret, clientRandom, serverRandom, isClient, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
}

View file

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha256"
"fmt"
"hash"
"sync/atomic"
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// TLSEcdhePskWithAes128CbcSha256 implements the TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 CipherSuite.
type TLSEcdhePskWithAes128CbcSha256 struct {
cbc atomic.Value // *cryptoCBC
}
// NewTLSEcdhePskWithAes128CbcSha256 creates TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 cipher.
func NewTLSEcdhePskWithAes128CbcSha256() *TLSEcdhePskWithAes128CbcSha256 {
return &TLSEcdhePskWithAes128CbcSha256{}
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSEcdhePskWithAes128CbcSha256) CertificateType() clientcertificate.Type {
return clientcertificate.Type(0)
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *TLSEcdhePskWithAes128CbcSha256) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return (KeyExchangeAlgorithmPsk | KeyExchangeAlgorithmEcdhe)
}
// ECC uses Elliptic Curve Cryptography.
func (c *TLSEcdhePskWithAes128CbcSha256) ECC() bool {
return true
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdhePskWithAes128CbcSha256) ID() ID {
return TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256
}
func (c *TLSEcdhePskWithAes128CbcSha256) String() string {
return "TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA256"
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *TLSEcdhePskWithAes128CbcSha256) HashFunc() func() hash.Hash {
return sha256.New
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *TLSEcdhePskWithAes128CbcSha256) AuthenticationType() AuthenticationType {
return AuthenticationTypePreSharedKey
}
// IsInitialized returns if the CipherSuite has keying material and can
// encrypt/decrypt packets.
func (c *TLSEcdhePskWithAes128CbcSha256) IsInitialized() bool {
return c.cbc.Load() != nil
}
// Init initializes the internal Cipher with keying material.
func (c *TLSEcdhePskWithAes128CbcSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const (
prfMacLen = 32
prfKeyLen = 16
prfIvLen = 16
)
keys, err := prf.GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc(),
)
if err != nil {
return err
}
var cbc *ciphersuite.CBC
if isClient {
cbc, err = ciphersuite.NewCBC(
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
c.HashFunc(),
)
} else {
cbc, err = ciphersuite.NewCBC(
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
c.HashFunc(),
)
}
c.cbc.Store(cbc)
return err
}
// Encrypt encrypts a single TLS RecordLayer.
func (c *TLSEcdhePskWithAes128CbcSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok { // !c.isInitialized()
return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
}
return cipherSuite.Encrypt(pkt, raw)
}
// Decrypt decrypts a single TLS RecordLayer.
func (c *TLSEcdhePskWithAes128CbcSha256) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok { // !c.isInitialized()
return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
}
return cipherSuite.Decrypt(h, raw)
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import "github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
// TLSEcdheRsaWithAes128GcmSha256 implements the TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuite.
type TLSEcdheRsaWithAes128GcmSha256 struct {
TLSEcdheEcdsaWithAes128GcmSha256
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSEcdheRsaWithAes128GcmSha256) CertificateType() clientcertificate.Type {
return clientcertificate.RSASign
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheRsaWithAes128GcmSha256) ID() ID {
return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
}
func (c *TLSEcdheRsaWithAes128GcmSha256) String() string {
return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import "github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
// TLSEcdheRsaWithAes256CbcSha implements the TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuite.
type TLSEcdheRsaWithAes256CbcSha struct {
TLSEcdheEcdsaWithAes256CbcSha
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSEcdheRsaWithAes256CbcSha) CertificateType() clientcertificate.Type {
return clientcertificate.RSASign
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheRsaWithAes256CbcSha) ID() ID {
return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
}
func (c *TLSEcdheRsaWithAes256CbcSha) String() string {
return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import "github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
// TLSEcdheRsaWithAes256GcmSha384 implements the TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 CipherSuite.
type TLSEcdheRsaWithAes256GcmSha384 struct {
TLSEcdheEcdsaWithAes256GcmSha384
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSEcdheRsaWithAes256GcmSha384) CertificateType() clientcertificate.Type {
return clientcertificate.RSASign
}
// ID returns the ID of the CipherSuite.
func (c *TLSEcdheRsaWithAes256GcmSha384) ID() ID {
return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
}
func (c *TLSEcdheRsaWithAes256GcmSha384) String() string {
return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
}

View file

@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/sha256"
"fmt"
"hash"
"sync/atomic"
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// TLSPskWithAes128CbcSha256 implements the TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuite.
type TLSPskWithAes128CbcSha256 struct {
cbc atomic.Value // *cryptoCBC
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSPskWithAes128CbcSha256) CertificateType() clientcertificate.Type {
return clientcertificate.Type(0)
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *TLSPskWithAes128CbcSha256) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return KeyExchangeAlgorithmPsk
}
// ECC uses Elliptic Curve Cryptography.
func (c *TLSPskWithAes128CbcSha256) ECC() bool {
return false
}
// ID returns the ID of the CipherSuite.
func (c *TLSPskWithAes128CbcSha256) ID() ID {
return TLS_PSK_WITH_AES_128_CBC_SHA256
}
func (c *TLSPskWithAes128CbcSha256) String() string {
return "TLS_PSK_WITH_AES_128_CBC_SHA256"
}
// HashFunc returns the hashing func for this CipherSuite.
func (c *TLSPskWithAes128CbcSha256) HashFunc() func() hash.Hash {
return sha256.New
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *TLSPskWithAes128CbcSha256) AuthenticationType() AuthenticationType {
return AuthenticationTypePreSharedKey
}
// IsInitialized returns if the CipherSuite has keying material and can
// encrypt/decrypt packets.
func (c *TLSPskWithAes128CbcSha256) IsInitialized() bool {
return c.cbc.Load() != nil
}
// Init initializes the internal Cipher with keying material.
func (c *TLSPskWithAes128CbcSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
const (
prfMacLen = 32
prfKeyLen = 16
prfIvLen = 16
)
keys, err := prf.GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc(),
)
if err != nil {
return err
}
var cbc *ciphersuite.CBC
if isClient {
cbc, err = ciphersuite.NewCBC(
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
c.HashFunc(),
)
} else {
cbc, err = ciphersuite.NewCBC(
keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
c.HashFunc(),
)
}
c.cbc.Store(cbc)
return err
}
// Encrypt encrypts a single TLS RecordLayer.
func (c *TLSPskWithAes128CbcSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok {
return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
}
return cipherSuite.Encrypt(pkt, raw)
}
// Decrypt decrypts a single TLS RecordLayer.
func (c *TLSPskWithAes128CbcSha256) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
cipherSuite, ok := c.cbc.Load().(*ciphersuite.CBC)
if !ok {
return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
}
return cipherSuite.Decrypt(h, raw)
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// NewTLSPskWithAes128Ccm returns the TLS_PSK_WITH_AES_128_CCM CipherSuite.
func NewTLSPskWithAes128Ccm() *Aes128Ccm {
return newAes128Ccm(
clientcertificate.Type(0),
TLS_PSK_WITH_AES_128_CCM,
true,
ciphersuite.CCMTagLength,
KeyExchangeAlgorithmPsk,
false,
)
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// NewTLSPskWithAes128Ccm8 returns the TLS_PSK_WITH_AES_128_CCM_8 CipherSuite.
func NewTLSPskWithAes128Ccm8() *Aes128Ccm {
return newAes128Ccm(
clientcertificate.Type(0),
TLS_PSK_WITH_AES_128_CCM_8,
true,
ciphersuite.CCMTagLength8,
KeyExchangeAlgorithmPsk,
false,
)
}

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import "github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
// TLSPskWithAes128GcmSha256 implements the TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuite.
type TLSPskWithAes128GcmSha256 struct {
TLSEcdheEcdsaWithAes128GcmSha256
}
// CertificateType returns what type of certificate this CipherSuite exchanges.
func (c *TLSPskWithAes128GcmSha256) CertificateType() clientcertificate.Type {
return clientcertificate.Type(0)
}
// KeyExchangeAlgorithm controls what key exchange algorithm is using during the handshake.
func (c *TLSPskWithAes128GcmSha256) KeyExchangeAlgorithm() KeyExchangeAlgorithm {
return KeyExchangeAlgorithmPsk
}
// ID returns the ID of the CipherSuite.
func (c *TLSPskWithAes128GcmSha256) ID() ID {
return TLS_PSK_WITH_AES_128_GCM_SHA256
}
func (c *TLSPskWithAes128GcmSha256) String() string {
return "TLS_PSK_WITH_AES_128_GCM_SHA256"
}
// AuthenticationType controls what authentication method is using during the handshake.
func (c *TLSPskWithAes128GcmSha256) AuthenticationType() AuthenticationType {
return AuthenticationTypePreSharedKey
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"github.com/pion/dtls/v3/pkg/crypto/ciphersuite"
"github.com/pion/dtls/v3/pkg/crypto/clientcertificate"
)
// NewTLSPskWithAes256Ccm8 returns the TLS_PSK_WITH_AES_256_CCM_8 CipherSuite.
func NewTLSPskWithAes256Ccm8() *Aes256Ccm {
return newAes256Ccm(
clientcertificate.Type(0),
TLS_PSK_WITH_AES_256_CCM_8,
true,
ciphersuite.CCMTagLength8,
KeyExchangeAlgorithmPsk,
false,
)
}

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package types
// AuthenticationType controls what authentication method is using during the handshake.
type AuthenticationType int
// AuthenticationType Enums.
const (
AuthenticationTypeCertificate AuthenticationType = iota + 1
AuthenticationTypePreSharedKey
AuthenticationTypeAnonymous
)

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package types provides types for TLS Ciphers
package types // nolint:revive
// KeyExchangeAlgorithm controls what exchange algorithm was chosen.
type KeyExchangeAlgorithm int
// KeyExchangeAlgorithm Bitmask.
const (
KeyExchangeAlgorithmNone KeyExchangeAlgorithm = 0
KeyExchangeAlgorithmPsk KeyExchangeAlgorithm = iota << 1
KeyExchangeAlgorithmEcdhe
)
// Has check if keyExchangeAlgorithm is supported.
func (a KeyExchangeAlgorithm) Has(v KeyExchangeAlgorithm) bool {
return (a & v) == v
}

View file

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package closer provides signaling channel for shutdown
package closer
import (
"context"
)
// Closer allows for each signaling a channel for shutdown.
type Closer struct {
ctx context.Context //nolint:containedctx
closeFunc func()
}
// NewCloser creates a new instance of Closer.
func NewCloser() *Closer {
ctx, closeFunc := context.WithCancel(context.Background())
return &Closer{
ctx: ctx,
closeFunc: closeFunc,
}
}
// NewCloserWithParent creates a new instance of Closer with a parent context.
func NewCloserWithParent(ctx context.Context) *Closer {
ctx, closeFunc := context.WithCancel(ctx)
return &Closer{
ctx: ctx,
closeFunc: closeFunc,
}
}
// Done returns a channel signaling when it is done.
func (c *Closer) Done() <-chan struct{} {
return c.ctx.Done()
}
// Err returns an error of the context.
func (c *Closer) Err() error {
return c.ctx.Err()
}
// Close sends a signal to trigger the ctx done channel.
func (c *Closer) Close() {
c.closeFunc()
}

242
vendor/github.com/pion/dtls/v3/internal/net/buffer.go generated vendored Normal file
View file

@ -0,0 +1,242 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package net implements DTLS specific networking primitives.
// NOTE: this package is an adaption of pion/transport/packetio that allows for
// storing a remote address alongside each packet in the buffer and implements
// relevant methods of net.PacketConn. If possible, the updates made in this
// repository will be reflected back upstream. If not, it is likely that this
// will be moved to a public package in this repository.
//
// This package was migrated from pion/transport/packetio at
// https://github.com/pion/transport/commit/6890c795c807a617c054149eee40a69d7fdfbfdb
package net
import (
"bytes"
"errors"
"io"
"net"
"sync"
"time"
"github.com/pion/transport/v4/deadline"
)
// ErrTimeout indicates that deadline was reached before operation could be
// completed.
var ErrTimeout = errors.New("buffer: i/o timeout")
// AddrPacket is a packet payload and the associated remote address from which
// it was received.
type AddrPacket struct {
addr net.Addr
data bytes.Buffer
}
// PacketBuffer is a circular buffer for network packets. Each slot in the
// buffer contains the remote address from which the packet was received, as
// well as the packet data.
type PacketBuffer struct {
mutex sync.Mutex
packets []AddrPacket
write, read int
// full indicates whether the buffer is full, which is needed to distinguish
// when the write pointer and read pointer are at the same index.
full bool
notify chan struct{}
closed bool
readDeadline *deadline.Deadline
}
// NewPacketBuffer creates a new PacketBuffer.
func NewPacketBuffer() *PacketBuffer {
return &PacketBuffer{
readDeadline: deadline.New(),
// In the narrow context in which this package is currently used, there
// will always be at least one packet written to the buffer. Therefore,
// we opt to allocate with size of 1 during construction, rather than
// waiting until that first packet is written.
packets: make([]AddrPacket, 1),
full: false,
}
}
// WriteTo writes a single packet to the buffer. The supplied address will
// remain associated with the packet.
func (b *PacketBuffer) WriteTo(pkt []byte, addr net.Addr) (int, error) {
b.mutex.Lock()
if b.closed {
b.mutex.Unlock()
return 0, io.ErrClosedPipe
}
var notify chan struct{}
if b.notify != nil {
notify = b.notify
b.notify = nil
}
// Check to see if we are full.
if b.full {
// If so, grow AddrPacket buffer.
var newSize int
if len(b.packets) < 128 {
// Double the number of packets.
newSize = len(b.packets) * 2
} else {
// Increase the number of packets by 25%.
newSize = 5 * len(b.packets) / 4
}
newBuf := make([]AddrPacket, newSize)
var n int
if b.read < b.write {
n = copy(newBuf, b.packets[b.read:b.write])
} else {
n = copy(newBuf, b.packets[b.read:])
n += copy(newBuf[n:], b.packets[:b.write])
}
b.packets = newBuf
// Update read/write pointers and mark buffer as not full.
b.read = 0
b.write = n
b.full = false
}
// Store the packet at the write pointer.
packet := &b.packets[b.write]
packet.data.Reset()
n, err := packet.data.Write(pkt)
if err != nil {
b.mutex.Unlock()
return n, err
}
packet.addr = addr
// Increment write pointer.
b.write++
// If the write pointer is equal to the length of the buffer, wrap around.
if len(b.packets) == b.write {
b.write = 0
}
// If a write resulted in making write and read pointers equivalent, then we
// are full.
if b.write == b.read {
b.full = true
}
b.mutex.Unlock()
if notify != nil {
close(notify)
}
return n, nil
}
// ReadFrom reads a single packet from the buffer, or blocks until one is
// available.
func (b *PacketBuffer) ReadFrom(packet []byte) (n int, addr net.Addr, err error) { //nolint:cyclop
select {
case <-b.readDeadline.Done():
return 0, nil, ErrTimeout
default:
}
for {
b.mutex.Lock()
if b.read != b.write || b.full {
ap := b.packets[b.read]
if len(packet) < ap.data.Len() {
b.mutex.Unlock()
return 0, nil, io.ErrShortBuffer
}
// Copy packet data from buffer.
n, err := ap.data.Read(packet)
if err != nil {
b.mutex.Unlock()
return n, nil, err
}
// Advance read pointer.
b.read++
if len(b.packets) == b.read {
b.read = 0
}
// If we were full before reading and have successfully read, we are
// no longer full.
if b.full {
b.full = false
}
b.mutex.Unlock()
return n, ap.addr, nil
}
if b.closed {
b.mutex.Unlock()
return 0, nil, io.EOF
}
if b.notify == nil {
b.notify = make(chan struct{})
}
notify := b.notify
b.mutex.Unlock()
select {
case <-b.readDeadline.Done():
return 0, nil, ErrTimeout
case <-notify:
}
}
}
// Close closes the buffer, allowing unread packets to be read, but erroring on
// any new writes.
func (b *PacketBuffer) Close() (err error) {
b.mutex.Lock()
if b.closed {
b.mutex.Unlock()
return nil
}
notify := b.notify
b.notify = nil
b.closed = true
b.mutex.Unlock()
if notify != nil {
close(notify)
}
return nil
}
// SetReadDeadline sets the read deadline for the buffer.
func (b *PacketBuffer) SetReadDeadline(t time.Time) error {
b.readDeadline.Set(t)
return nil
}

View file

@ -0,0 +1,413 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package udp implements DTLS specific UDP networking primitives.
// NOTE: this package is an adaption of pion/transport/udp that allows for
// routing datagrams based on identifiers other than the remote address. The
// primary use case for this functionality is routing based on DTLS connection
// IDs. In order to allow for consumers of this package to treat connections as
// generic net.PackageConn, routing and identitier establishment is based on
// custom introspecion of datagrams, rather than direct intervention by
// consumers. If possible, the updates made in this repository will be reflected
// back upstream. If not, it is likely that this will be moved to a public
// package in this repository.
//
// This package was migrated from pion/transport/udp at
// https://github.com/pion/transport/commit/6890c795c807a617c054149eee40a69d7fdfbfdb
package udp
import (
"context"
"errors"
"net"
"sync"
"sync/atomic"
"time"
idtlsnet "github.com/pion/dtls/v3/internal/net"
dtlsnet "github.com/pion/dtls/v3/pkg/net"
"github.com/pion/transport/v4/deadline"
)
const (
receiveMTU = 8192
defaultListenBacklog = 128 // same as Linux default
)
// Typed errors.
var (
ErrClosedListener = errors.New("udp: listener closed")
ErrListenQueueExceeded = errors.New("udp: listen queue exceeded")
)
// listener augments a connection-oriented Listener over a UDP PacketConn.
type listener struct {
pConn *net.UDPConn
accepting atomic.Value // bool
acceptCh chan *PacketConn
doneCh chan struct{}
doneOnce sync.Once
acceptFilter func([]byte) bool
datagramRouter func([]byte) (string, bool)
connIdentifier func([]byte) (string, bool)
connLock sync.Mutex
conns map[string]*PacketConn
connWG sync.WaitGroup
readWG sync.WaitGroup
errClose atomic.Value // error
readDoneCh chan struct{}
errRead atomic.Value // error
}
// Accept waits for and returns the next connection to the listener.
func (l *listener) Accept() (net.PacketConn, net.Addr, error) {
select {
case c := <-l.acceptCh:
l.connWG.Add(1)
return c, c.raddr, nil
case <-l.readDoneCh:
err, _ := l.errRead.Load().(error)
return nil, nil, err
case <-l.doneCh:
return nil, nil, ErrClosedListener
}
}
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (l *listener) Close() error {
var err error
l.doneOnce.Do(func() {
l.accepting.Store(false)
close(l.doneCh)
l.connLock.Lock()
// Close unaccepted connections
lclose:
for {
select {
case c := <-l.acceptCh:
close(c.doneCh)
// If we have an alternate identifier, remove it from the connection
// map.
if id := c.id.Load(); id != nil {
delete(l.conns, id.(string)) //nolint:forcetypeassert
}
// If we haven't already removed the remote address, remove it
// from the connection map.
if c.rmraddr.Load() == nil {
delete(l.conns, c.raddr.String())
c.rmraddr.Store(true)
}
default:
break lclose
}
}
nConns := len(l.conns)
l.connLock.Unlock()
l.connWG.Done()
if nConns == 0 {
// Wait if this is the final connection.
l.readWG.Wait()
if errClose, ok := l.errClose.Load().(error); ok {
err = errClose
}
} else {
err = nil
}
})
return err
}
// Addr returns the listener's network address.
func (l *listener) Addr() net.Addr {
return l.pConn.LocalAddr()
}
// ListenConfig stores options for listening to an address.
type ListenConfig struct {
// Backlog defines the maximum length of the queue of pending
// connections. It is equivalent of the backlog argument of
// POSIX listen function.
// If a connection request arrives when the queue is full,
// the request will be silently discarded, unlike TCP.
// Set zero to use default value 128 which is same as Linux default.
Backlog int
// AcceptFilter determines whether the new conn should be made for
// the incoming packet. If not set, any packet creates new conn.
AcceptFilter func([]byte) bool
// DatagramRouter routes an incoming datagram to a connection by extracting
// an identifier from the its paylod
DatagramRouter func([]byte) (string, bool)
// ConnectionIdentifier extracts an identifier from an outgoing packet. If
// the identifier is not already associated with the connection, it will be
// added.
ConnectionIdentifier func([]byte) (string, bool)
}
// Listen creates a new listener based on the ListenConfig.
func (lc *ListenConfig) Listen(network string, laddr *net.UDPAddr) (dtlsnet.PacketListener, error) {
if lc.Backlog == 0 {
lc.Backlog = defaultListenBacklog
}
conn, err := net.ListenUDP(network, laddr)
if err != nil {
return nil, err
}
packetListener := &listener{
pConn: conn,
acceptCh: make(chan *PacketConn, lc.Backlog),
conns: make(map[string]*PacketConn),
doneCh: make(chan struct{}),
acceptFilter: lc.AcceptFilter,
datagramRouter: lc.DatagramRouter,
connIdentifier: lc.ConnectionIdentifier,
readDoneCh: make(chan struct{}),
}
packetListener.accepting.Store(true)
packetListener.connWG.Add(1)
packetListener.readWG.Add(2) // wait readLoop and Close execution routine
go packetListener.readLoop()
go func() {
packetListener.connWG.Wait()
if err := packetListener.pConn.Close(); err != nil {
packetListener.errClose.Store(err)
}
packetListener.readWG.Done()
}()
return packetListener, nil
}
// Listen creates a new listener using default ListenConfig.
func Listen(network string, laddr *net.UDPAddr) (dtlsnet.PacketListener, error) {
return (&ListenConfig{}).Listen(network, laddr)
}
// readLoop dispatches packets to the proper connection, creating a new one if
// necessary, until all connections are closed.
func (l *listener) readLoop() {
defer l.readWG.Done()
defer close(l.readDoneCh)
buf := make([]byte, receiveMTU)
for {
n, raddr, err := l.pConn.ReadFrom(buf)
if err != nil {
l.errRead.Store(err)
return
}
conn, ok, err := l.getConn(raddr, buf[:n])
if err != nil {
continue
}
if ok {
_, _ = conn.buffer.WriteTo(buf[:n], raddr)
}
}
}
// getConn gets an existing connection or creates a new one.
func (l *listener) getConn(raddr net.Addr, buf []byte) (*PacketConn, bool, error) { //nolint:cyclop
l.connLock.Lock()
defer l.connLock.Unlock()
// If we have a custom resolver, use it.
if l.datagramRouter != nil {
if id, ok := l.datagramRouter(buf); ok {
if conn, ok := l.conns[id]; ok {
return conn, true, nil
}
}
}
// If we don't have a custom resolver, or we were unable to find an
// associated connection, fall back to remote address.
conn, ok := l.conns[raddr.String()]
if !ok {
if isAccepting, ok := l.accepting.Load().(bool); !isAccepting || !ok {
return nil, false, ErrClosedListener
}
if l.acceptFilter != nil {
if !l.acceptFilter(buf) {
return nil, false, nil
}
}
conn = l.newPacketConn(raddr)
select {
case l.acceptCh <- conn:
l.conns[raddr.String()] = conn
default:
return nil, false, ErrListenQueueExceeded
}
}
return conn, true, nil
}
// PacketConn is a net.PacketConn implementation that is able to dictate its
// routing ID via an alternate identifier from its remote address. Internal
// buffering is performed for reads, and writes are passed through to the
// underlying net.PacketConn.
type PacketConn struct {
listener *listener
raddr net.Addr
rmraddr atomic.Value // bool
id atomic.Value // string
buffer *idtlsnet.PacketBuffer
doneCh chan struct{}
doneOnce sync.Once
writeDeadline *deadline.Deadline
}
// newPacketConn constructs a new PacketConn.
func (l *listener) newPacketConn(raddr net.Addr) *PacketConn {
return &PacketConn{
listener: l,
raddr: raddr,
buffer: idtlsnet.NewPacketBuffer(),
doneCh: make(chan struct{}),
writeDeadline: deadline.New(),
}
}
// ReadFrom reads a single packet payload and its associated remote address from
// the underlying buffer.
func (c *PacketConn) ReadFrom(buff []byte) (int, net.Addr, error) {
return c.buffer.ReadFrom(buff)
}
// WriteTo writes len(payload) bytes from payload to the specified address.
func (c *PacketConn) WriteTo(payload []byte, addr net.Addr) (n int, err error) {
// If we have a connection identifier, check to see if the outgoing packet
// sets it.
if c.listener.connIdentifier != nil {
id := c.id.Load()
// Only update establish identifier if we haven't already done so.
if id == nil {
candidate, ok := c.listener.connIdentifier(payload)
// If we have an identifier, add entry to connection map.
if ok {
c.listener.connLock.Lock()
c.listener.conns[candidate] = c
c.listener.connLock.Unlock()
c.id.Store(candidate)
}
}
// If we are writing to a remote address that differs from the initial,
// we have an alternate identifier established, and we haven't already
// freed the remote address, free the remote address to be used by
// another connection.
// Note: this strategy results in holding onto a remote address after it
// is potentially no longer in use by the client. However, releasing
// earlier means that we could miss some packets that should have been
// routed to this connection. Ideally, we would drop the connection
// entry for the remote address as soon as the client starts sending
// using an alternate identifier, but in practice this proves
// challenging because any client could spoof a connection identifier,
// resulting in the remote address entry being dropped prior to the
// "real" client transitioning to sending using the alternate
// identifier.
if id != nil && c.rmraddr.Load() == nil && addr.String() != c.raddr.String() {
c.listener.connLock.Lock()
delete(c.listener.conns, c.raddr.String())
c.rmraddr.Store(true)
c.listener.connLock.Unlock()
}
}
select {
case <-c.writeDeadline.Done():
return 0, context.DeadlineExceeded
default:
}
return c.listener.pConn.WriteTo(payload, addr)
}
// Close closes the conn and releases any Read calls.
func (c *PacketConn) Close() error {
var err error
c.doneOnce.Do(func() {
c.listener.connWG.Done()
close(c.doneCh)
c.listener.connLock.Lock()
// If we have an alternate identifier, remove it from the connection
// map.
if id := c.id.Load(); id != nil {
delete(c.listener.conns, id.(string)) //nolint:forcetypeassert
}
// If we haven't already removed the remote address, remove it from the
// connection map.
if c.rmraddr.Load() == nil {
delete(c.listener.conns, c.raddr.String())
c.rmraddr.Store(true)
}
nConns := len(c.listener.conns)
c.listener.connLock.Unlock()
if isAccepting, ok := c.listener.accepting.Load().(bool); nConns == 0 && !isAccepting && ok {
// Wait if this is the final connection
c.listener.readWG.Wait()
if errClose, ok := c.listener.errClose.Load().(error); ok {
err = errClose
}
} else {
err = nil
}
if errBuf := c.buffer.Close(); errBuf != nil && err == nil {
err = errBuf
}
})
return err
}
// LocalAddr implements net.PacketConn.LocalAddr.
func (c *PacketConn) LocalAddr() net.Addr {
return c.listener.pConn.LocalAddr()
}
// SetDeadline implements net.PacketConn.SetDeadline.
func (c *PacketConn) SetDeadline(t time.Time) error {
c.writeDeadline.Set(t)
return c.SetReadDeadline(t)
}
// SetReadDeadline implements net.PacketConn.SetReadDeadline.
func (c *PacketConn) SetReadDeadline(t time.Time) error {
return c.buffer.SetReadDeadline(t)
}
// SetWriteDeadline implements net.PacketConn.SetWriteDeadline.
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
c.writeDeadline.Set(t)
// Write deadline of underlying connection should not be changed
// since the connection can be shared.
return nil
}

53
vendor/github.com/pion/dtls/v3/internal/util/util.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package util contains small helpers used across the repo
package util // nolint:revive
import (
"encoding/binary"
"golang.org/x/crypto/cryptobyte"
)
// BigEndianUint24 returns the value of a big endian uint24.
func BigEndianUint24(raw []byte) uint32 {
if len(raw) < 3 {
return 0
}
rawCopy := make([]byte, 4)
copy(rawCopy[1:], raw)
return binary.BigEndian.Uint32(rawCopy)
}
// PutBigEndianUint24 encodes a uint24 and places into out.
func PutBigEndianUint24(out []byte, in uint32) {
tmp := make([]byte, 4)
binary.BigEndian.PutUint32(tmp, in)
copy(out, tmp[1:])
}
// PutBigEndianUint48 encodes a uint64 and places into out.
func PutBigEndianUint48(out []byte, in uint64) {
tmp := make([]byte, 8)
binary.BigEndian.PutUint64(tmp, in)
copy(out, tmp[2:])
}
// Max returns the larger value.
func Max(a, b int) int {
if a > b {
return a
}
return b
}
// AddUint48 appends a big-endian, 48-bit value to the byte string.
// Remove if / when https://github.com/golang/crypto/pull/265 is merged
// upstream.
func AddUint48(b *cryptobyte.Builder, v uint64) {
b.AddBytes([]byte{byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)})
}

115
vendor/github.com/pion/dtls/v3/listener.go generated vendored Normal file
View file

@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"net"
"github.com/pion/dtls/v3/internal/net/udp"
dtlsnet "github.com/pion/dtls/v3/pkg/net"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// Listen creates a DTLS listener.
//
// Deprecated: Use ListenWithOptions instead.
func Listen(network string, laddr *net.UDPAddr, config *Config) (net.Listener, error) {
if err := validateConfig(config); err != nil {
return nil, err
}
lc := udp.ListenConfig{
AcceptFilter: func(packet []byte) bool {
pkts, err := recordlayer.UnpackDatagram(packet)
if err != nil || len(pkts) < 1 {
return false
}
h := &recordlayer.Header{}
if err := h.Unmarshal(pkts[0]); err != nil {
return false
}
return h.ContentType == protocol.ContentTypeHandshake
},
}
// If connection ID support is enabled, then they must be supported in
// routing.
if config.ConnectionIDGenerator != nil {
lc.DatagramRouter = cidDatagramRouter(len(config.ConnectionIDGenerator()))
lc.ConnectionIdentifier = cidConnIdentifier()
}
parent, err := lc.Listen(network, laddr)
if err != nil {
return nil, err
}
return &listener{
config: config,
parent: parent,
}, nil
}
// ListenWithOptions creates a DTLS listener.
func ListenWithOptions(network string, laddr *net.UDPAddr, opts ...ServerOption) (net.Listener, error) {
config, err := buildServerConfig(opts...)
if err != nil {
return nil, err
}
return Listen(network, laddr, config)
}
// NewListener creates a DTLS listener which accepts connections from an inner Listener.
//
// Deprecated: Use NewListenerWithOptions instead.
func NewListener(inner dtlsnet.PacketListener, config *Config) (net.Listener, error) {
if err := validateConfig(config); err != nil {
return nil, err
}
return &listener{
config: config,
parent: inner,
}, nil
}
// NewListenerWithOptions creates a DTLS listener which accepts connections from an inner Listener.
func NewListenerWithOptions(inner dtlsnet.PacketListener, opts ...ServerOption) (net.Listener, error) {
config, err := buildServerConfig(opts...)
if err != nil {
return nil, err
}
return NewListener(inner, config)
}
// listener represents a DTLS listener.
type listener struct {
config *Config
parent dtlsnet.PacketListener
}
// Accept waits for and returns the next connection to the listener.
// You have to either close or read on all connection that are created.
func (l *listener) Accept() (net.Conn, error) {
c, raddr, err := l.parent.Accept()
if err != nil {
return nil, err
}
return serverWithConfig(c, raddr, l.config)
}
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
// Already Accepted connections are not closed.
func (l *listener) Close() error {
return l.parent.Close()
}
// Addr returns the listener's network address.
func (l *listener) Addr() net.Addr {
return l.parent.Addr()
}

656
vendor/github.com/pion/dtls/v3/options.go generated vendored Normal file
View file

@ -0,0 +1,656 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"crypto/tls"
"crypto/x509"
"io"
"net"
"time"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/logging"
)
// ServerOption configures a DTLS server.
type ServerOption interface {
applyServer(*dtlsConfig) error
}
// ClientOption configures a DTLS client.
type ClientOption interface {
applyClient(*dtlsConfig) error
}
// Option is an option that can be used with both client and server.
// This is used for options that apply to both sides of a connection,
// such as in the Resume function where the side is determined at runtime.
type Option interface {
ServerOption
ClientOption
}
// defensiveCopy copies a slice. This prevents the caller from mutating
// the config after construction. Returns empty slice if input is empty.
func defensiveCopy[T any](t ...T) []T {
return append([]T{}, t...)
}
// dtlsConfig is the internal configuration structure.
// This will eventually replace the exported Config struct.
type dtlsConfig struct { //nolint:dupl
certificates []tls.Certificate
cipherSuites []CipherSuiteID
customCipherSuites func() []CipherSuite
signatureSchemes []tls.SignatureScheme
certificateSignatureSchemes []tls.SignatureScheme
srtpProtectionProfiles []SRTPProtectionProfile
srtpMasterKeyIdentifier []byte
clientAuth ClientAuthType
extendedMasterSecret ExtendedMasterSecretType
flightInterval time.Duration
disableRetransmitBackoff bool
psk PSKCallback
pskIdentityHint []byte
insecureSkipVerify bool
insecureHashes bool
verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
verifyConnection func(*State) error
rootCAs *x509.CertPool
clientCAs *x509.CertPool
serverName string
loggerFactory logging.LoggerFactory
mtu int
replayProtectionWindow int
keyLogWriter io.Writer
sessionStore SessionStore
supportedProtocols []string
ellipticCurves []elliptic.Curve
getCertificate func(*ClientHelloInfo) (*tls.Certificate, error)
getClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error)
insecureSkipVerifyHello bool
connectionIDGenerator func() []byte
paddingLengthGenerator func(uint) uint
helloRandomBytesGenerator func() [handshake.RandomBytesLength]byte
clientHelloMessageHook func(handshake.MessageClientHello) handshake.Message
serverHelloMessageHook func(handshake.MessageServerHello) handshake.Message
certificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message
onConnectionAttempt func(net.Addr) error
}
// applyDefaults applies default values to the config.
func (c *dtlsConfig) applyDefaults() {
c.extendedMasterSecret = RequestExtendedMasterSecret
c.flightInterval = time.Second
c.mtu = defaultMTU
c.replayProtectionWindow = defaultReplayProtectionWindow
}
// toConfig converts internal dtlsConfig to the exported Config struct.
// This is for backward compatibility and will be removed when Config is deprecated.
// All slice fields are copied to ensure immutability.
func (c *dtlsConfig) toConfig() *Config {
config := &Config{
CustomCipherSuites: c.customCipherSuites,
ClientAuth: c.clientAuth,
ExtendedMasterSecret: c.extendedMasterSecret,
FlightInterval: c.flightInterval,
DisableRetransmitBackoff: c.disableRetransmitBackoff,
PSK: c.psk,
InsecureSkipVerify: c.insecureSkipVerify,
InsecureHashes: c.insecureHashes,
VerifyPeerCertificate: c.verifyPeerCertificate,
VerifyConnection: c.verifyConnection,
RootCAs: c.rootCAs,
ClientCAs: c.clientCAs,
ServerName: c.serverName,
LoggerFactory: c.loggerFactory,
MTU: c.mtu,
ReplayProtectionWindow: c.replayProtectionWindow,
KeyLogWriter: c.keyLogWriter,
SessionStore: c.sessionStore,
GetCertificate: c.getCertificate,
GetClientCertificate: c.getClientCertificate,
InsecureSkipVerifyHello: c.insecureSkipVerifyHello,
ConnectionIDGenerator: c.connectionIDGenerator,
PaddingLengthGenerator: c.paddingLengthGenerator,
HelloRandomBytesGenerator: c.helloRandomBytesGenerator,
ClientHelloMessageHook: c.clientHelloMessageHook,
ServerHelloMessageHook: c.serverHelloMessageHook,
CertificateRequestMessageHook: c.certificateRequestMessageHook,
OnConnectionAttempt: c.onConnectionAttempt,
}
if len(c.certificates) > 0 {
config.Certificates = append([]tls.Certificate(nil), c.certificates...)
}
if len(c.cipherSuites) > 0 {
config.CipherSuites = append([]CipherSuiteID(nil), c.cipherSuites...)
}
if len(c.signatureSchemes) > 0 {
config.SignatureSchemes = append([]tls.SignatureScheme(nil), c.signatureSchemes...)
}
if len(c.certificateSignatureSchemes) > 0 {
config.CertificateSignatureSchemes = append([]tls.SignatureScheme(nil), c.certificateSignatureSchemes...)
}
if len(c.srtpProtectionProfiles) > 0 {
config.SRTPProtectionProfiles = append([]SRTPProtectionProfile(nil), c.srtpProtectionProfiles...)
}
if len(c.srtpMasterKeyIdentifier) > 0 {
config.SRTPMasterKeyIdentifier = append([]byte(nil), c.srtpMasterKeyIdentifier...)
}
if len(c.pskIdentityHint) > 0 {
config.PSKIdentityHint = append([]byte(nil), c.pskIdentityHint...)
}
if len(c.supportedProtocols) > 0 {
config.SupportedProtocols = append([]string(nil), c.supportedProtocols...)
}
if len(c.ellipticCurves) > 0 {
config.EllipticCurves = append([]elliptic.Curve(nil), c.ellipticCurves...)
}
return config
}
// buildConfig builds a Config from the provided options, for mixed client/server cases.
func buildConfig(opts ...Option) (*Config, error) {
cfg := &dtlsConfig{}
cfg.applyDefaults()
for _, opt := range opts {
if err := opt.applyServer(cfg); err != nil {
return nil, err
}
}
return cfg.toConfig(), nil
}
// buildServerConfig builds a Config for server from the provided options.
func buildServerConfig(opts ...ServerOption) (*Config, error) {
cfg := &dtlsConfig{}
cfg.applyDefaults()
for _, opt := range opts {
if err := opt.applyServer(cfg); err != nil {
return nil, err
}
}
return cfg.toConfig(), nil
}
// buildClientConfig builds a Config for client from the provided options.
func buildClientConfig(opts ...ClientOption) (*Config, error) {
cfg := &dtlsConfig{}
cfg.applyDefaults()
for _, opt := range opts {
if err := opt.applyClient(cfg); err != nil {
return nil, err
}
}
return cfg.toConfig(), nil
}
// sharedOption wraps an apply function that works for both client and server.
// This eliminates code duplication for options that behave identically on both sides.
type sharedOption func(*dtlsConfig) error
func (o sharedOption) applyServer(c *dtlsConfig) error { return o(c) }
func (o sharedOption) applyClient(c *dtlsConfig) error { return o(c) }
// WithCertificates sets the certificate chain to present to the other side of the connection.
// For functional options, an explicitly empty slice is not allowed.
func WithCertificates(certs ...tls.Certificate) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(certs) == 0 {
return errEmptyCertificates
}
c.certificates = defensiveCopy(certs...)
return nil
})
}
// WithCipherSuites sets the supported cipher suites.
// For functional options, an explicitly empty slice is not allowed.
func WithCipherSuites(suites ...CipherSuiteID) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(suites) == 0 {
return errEmptyCipherSuites
}
c.cipherSuites = defensiveCopy(suites...)
return nil
})
}
// WithCustomCipherSuites sets the custom cipher suites provider.
// Returns an error if the provider is nil.
func WithCustomCipherSuites(fn func() []CipherSuite) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilCustomCipherSuites
}
c.customCipherSuites = fn
return nil
})
}
// WithSignatureSchemes sets the signature schemes.
// For functional options, an explicitly empty slice is not allowed.
func WithSignatureSchemes(schemes ...tls.SignatureScheme) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(schemes) == 0 {
return errEmptySignatureSchemes
}
c.signatureSchemes = defensiveCopy(schemes...)
return nil
})
}
// WithCertificateSignatureSchemes sets the signature and hash schemes that may be used
// in digital signatures for X.509 certificates. If not set, the signature_algorithms_cert
// extension is not sent, and SignatureSchemes is used for both handshake signatures and
// certificate chain validation, as specified in RFC 8446 Section 4.2.3.
// For functional options, an explicitly empty slice is not allowed.
func WithCertificateSignatureSchemes(schemes ...tls.SignatureScheme) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(schemes) == 0 {
return errEmptyCertificateSignatureSchemes
}
c.certificateSignatureSchemes = defensiveCopy(schemes...)
return nil
})
}
// WithSRTPProtectionProfiles sets the SRTP protection profiles.
// For functional options, an explicitly empty slice is not allowed.
func WithSRTPProtectionProfiles(profiles ...SRTPProtectionProfile) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(profiles) == 0 {
return errEmptySRTPProtectionProfiles
}
c.srtpProtectionProfiles = defensiveCopy(profiles...)
return nil
})
}
// WithSRTPMasterKeyIdentifier sets the SRTP master key identifier.
func WithSRTPMasterKeyIdentifier(identifier []byte) Option {
return sharedOption(func(c *dtlsConfig) error {
c.srtpMasterKeyIdentifier = defensiveCopy(identifier...)
return nil
})
}
// WithExtendedMasterSecret sets the extended master secret policy.
// Returns an error if the type is invalid.
func WithExtendedMasterSecret(ems ExtendedMasterSecretType) Option {
return sharedOption(func(c *dtlsConfig) error {
if ems < RequestExtendedMasterSecret || ems > DisableExtendedMasterSecret {
return errInvalidExtendedMasterSecretType
}
c.extendedMasterSecret = ems
return nil
})
}
// WithFlightInterval sets the flight interval for handshake messages.
// Returns an error if the interval is not positive.
func WithFlightInterval(interval time.Duration) Option {
return sharedOption(func(c *dtlsConfig) error {
if interval <= 0 {
return errInvalidFlightInterval
}
c.flightInterval = interval
return nil
})
}
// WithDisableRetransmitBackoff disables retransmit backoff.
func WithDisableRetransmitBackoff(disable bool) Option {
return sharedOption(func(c *dtlsConfig) error {
c.disableRetransmitBackoff = disable
return nil
})
}
// WithPSK sets the pre-shared key callback.
// Returns an error if the callback is nil.
func WithPSK(callback PSKCallback) Option {
return sharedOption(func(c *dtlsConfig) error {
if callback == nil {
return errNilPSKCallback
}
c.psk = callback
return nil
})
}
// WithPSKIdentityHint sets the PSK identity hint.
func WithPSKIdentityHint(hint []byte) Option {
return sharedOption(func(c *dtlsConfig) error {
c.pskIdentityHint = defensiveCopy(hint...)
return nil
})
}
// WithInsecureSkipVerify skips certificate verification.
// This should only be used for testing.
func WithInsecureSkipVerify(skip bool) Option {
return sharedOption(func(c *dtlsConfig) error {
c.insecureSkipVerify = skip
return nil
})
}
// WithInsecureHashes allows the use of insecure hash algorithms.
func WithInsecureHashes(allow bool) Option {
return sharedOption(func(c *dtlsConfig) error {
c.insecureHashes = allow
return nil
})
}
// WithVerifyPeerCertificate sets the peer certificate verification callback.
// Returns an error if the callback is nil.
func WithVerifyPeerCertificate(fn func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilVerifyPeerCertificate
}
c.verifyPeerCertificate = fn
return nil
})
}
// WithVerifyConnection sets the connection verification callback.
// Returns an error if the callback is nil.
func WithVerifyConnection(fn func(*State) error) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilVerifyConnection
}
c.verifyConnection = fn
return nil
})
}
// WithRootCAs sets the root certificate authorities.
func WithRootCAs(pool *x509.CertPool) Option {
return sharedOption(func(c *dtlsConfig) error {
c.rootCAs = pool
return nil
})
}
// WithServerName sets the server name for certificate verification.
func WithServerName(name string) Option {
return sharedOption(func(c *dtlsConfig) error {
c.serverName = name
return nil
})
}
// WithLoggerFactory sets the logger factory for creating loggers.
func WithLoggerFactory(factory logging.LoggerFactory) Option {
return sharedOption(func(c *dtlsConfig) error {
c.loggerFactory = factory
return nil
})
}
// WithMTU sets the maximum transmission unit.
// Returns an error if the MTU is not positive.
func WithMTU(mtu int) Option {
return sharedOption(func(c *dtlsConfig) error {
if mtu <= 0 {
return errInvalidMTU
}
c.mtu = mtu
return nil
})
}
// WithReplayProtectionWindow sets the replay protection window size.
// Returns an error if the window size is negative.
func WithReplayProtectionWindow(window int) Option {
return sharedOption(func(c *dtlsConfig) error {
if window < 0 {
return errInvalidReplayProtectionWindow
}
c.replayProtectionWindow = window
return nil
})
}
// WithKeyLogWriter sets the key log writer for debugging.
// Use of KeyLogWriter compromises security and should only be used for debugging.
func WithKeyLogWriter(writer io.Writer) Option {
return sharedOption(func(c *dtlsConfig) error {
c.keyLogWriter = writer
return nil
})
}
// WithSessionStore sets the session store for resumption.
func WithSessionStore(store SessionStore) Option {
return sharedOption(func(c *dtlsConfig) error {
c.sessionStore = store
return nil
})
}
// WithSupportedProtocols sets the supported application protocols for ALPN.
// For functional options, an explicitly empty slice is not allowed.
func WithSupportedProtocols(protocols ...string) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(protocols) == 0 {
return errEmptySupportedProtocols
}
c.supportedProtocols = defensiveCopy(protocols...)
return nil
})
}
// WithEllipticCurves sets the elliptic curves.
// For functional options, an explicitly empty slice is not allowed.
func WithEllipticCurves(curves ...elliptic.Curve) Option {
return sharedOption(func(c *dtlsConfig) error {
if len(curves) == 0 {
return errEmptyEllipticCurves
}
c.ellipticCurves = defensiveCopy(curves...)
return nil
})
}
// WithGetClientCertificate sets the client certificate getter callback.
// Returns an error if the callback is nil.
func WithGetClientCertificate(fn func(*CertificateRequestInfo) (*tls.Certificate, error)) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilGetClientCertificate
}
c.getClientCertificate = fn
return nil
})
}
// WithConnectionIDGenerator sets the connection ID generator.
// Returns an error if the generator is nil.
func WithConnectionIDGenerator(fn func() []byte) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilConnectionIDGenerator
}
c.connectionIDGenerator = fn
return nil
})
}
// WithPaddingLengthGenerator sets the padding length generator.
// Returns an error if the generator is nil.
func WithPaddingLengthGenerator(fn func(uint) uint) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilPaddingLengthGenerator
}
c.paddingLengthGenerator = fn
return nil
})
}
// WithHelloRandomBytesGenerator sets the hello random bytes generator.
// Returns an error if the generator is nil.
func WithHelloRandomBytesGenerator(fn func() [handshake.RandomBytesLength]byte) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilHelloRandomBytesGenerator
}
c.helloRandomBytesGenerator = fn
return nil
})
}
// WithClientHelloMessageHook sets the client hello message hook.
// Returns an error if the hook is nil.
func WithClientHelloMessageHook(fn func(handshake.MessageClientHello) handshake.Message) Option {
return sharedOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilClientHelloMessageHook
}
c.clientHelloMessageHook = fn
return nil
})
}
// serverOnlyOption wraps an apply function for server-only options.
type serverOnlyOption func(*dtlsConfig) error
func (o serverOnlyOption) applyServer(c *dtlsConfig) error { return o(c) }
// WithClientAuth sets the client authentication policy.
// Returns an error if the type is invalid.
// This option is only applicable to servers.
func WithClientAuth(auth ClientAuthType) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
if auth < NoClientCert || auth > RequireAndVerifyClientCert {
return errInvalidClientAuthType
}
c.clientAuth = auth
return nil
})
}
// WithClientCAs sets the client certificate authorities.
// This option is only applicable to servers.
func WithClientCAs(pool *x509.CertPool) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
c.clientCAs = pool
return nil
})
}
// WithGetCertificate sets the certificate getter callback.
// Returns an error if the callback is nil.
// This option is only applicable to servers.
func WithGetCertificate(fn func(*ClientHelloInfo) (*tls.Certificate, error)) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilGetCertificate
}
c.getCertificate = fn
return nil
})
}
// WithInsecureSkipVerifyHello skips hello verify phase on the server.
// This has implication on DoS attack resistance.
// This option is only applicable to servers.
func WithInsecureSkipVerifyHello(skip bool) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
c.insecureSkipVerifyHello = skip
return nil
})
}
// WithServerHelloMessageHook sets the server hello message hook.
// Returns an error if the hook is nil.
// This option is only applicable to servers.
func WithServerHelloMessageHook(fn func(handshake.MessageServerHello) handshake.Message) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilServerHelloMessageHook
}
c.serverHelloMessageHook = fn
return nil
})
}
// WithCertificateRequestMessageHook sets the certificate request message hook.
// Returns an error if the hook is nil.
// This option is only applicable to servers.
func WithCertificateRequestMessageHook(fn func(handshake.MessageCertificateRequest) handshake.Message) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilCertificateRequestMessageHook
}
c.certificateRequestMessageHook = fn
return nil
})
}
// WithOnConnectionAttempt sets the connection attempt callback.
// Returns an error if the callback is nil.
// This option is only applicable to servers.
func WithOnConnectionAttempt(fn func(net.Addr) error) ServerOption {
return serverOnlyOption(func(c *dtlsConfig) error {
if fn == nil {
return errNilOnConnectionAttempt
}
c.onConnectionAttempt = fn
return nil
})
}

15
vendor/github.com/pion/dtls/v3/packet.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package dtls
import (
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
type packet struct {
record *recordlayer.RecordLayer
shouldEncrypt bool
shouldWrapCID bool
resetLocalSequenceNumber bool
}

260
vendor/github.com/pion/dtls/v3/pkg/crypto/ccm/ccm.go generated vendored Normal file
View file

@ -0,0 +1,260 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package ccm implements a CCM, Counter with CBC-MAC
// as per RFC 3610.
//
// See https://tools.ietf.org/html/rfc3610
//
// This code was lifted from https://github.com/bocajim/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go
// and as such was not written by the Pions authors. Like Pions this
// code is licensed under MIT.
//
// A request for including CCM into the Go standard library
// can be found as issue #27484 on the https://github.com/golang/go/
// repository.
package ccm
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"errors"
"math"
)
// ccm represents a Counter with CBC-MAC with a specific key.
type ccm struct {
b cipher.Block
M uint8
L uint8
}
const ccmBlockSize = 16
// CCM is a block cipher in Counter with CBC-MAC mode.
// Providing authenticated encryption with associated data via the cipher.AEAD interface.
type CCM interface {
cipher.AEAD
// MaxLength returns the maxium length of plaintext in calls to Seal.
// The maximum length of ciphertext in calls to Open is MaxLength()+Overhead().
// The maximum length is related to CCM's `L` parameter (15-noncesize) and
// is 1<<(8*L) - 1 (but also limited by the maxium size of an int).
MaxLength() int
}
var (
errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher")
errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16")
errInvalidNonceSize = errors.New("ccm: invalid nonce size")
)
// NewCCM returns the given 128-bit block cipher wrapped in CCM.
// The tagsize must be an even integer between 4 and 16 inclusive
// and is used as CCM's `M` parameter.
// The noncesize must be an integer between 7 and 13 inclusive,
// 15-noncesize is used as CCM's `L` parameter.
func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) {
if b.BlockSize() != ccmBlockSize {
return nil, errInvalidBlockSize
}
if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 {
return nil, errInvalidTagSize
}
lensize := 15 - noncesize
if lensize < 2 || lensize > 8 {
return nil, errInvalidNonceSize
}
c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)} //nolint:gosec // G114
return c, nil
}
func (c *ccm) NonceSize() int { return 15 - int(c.L) }
func (c *ccm) Overhead() int { return int(c.M) }
func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) }
func maxlen(l uint8, tagsize int) int {
mLen := (uint64(1) << (8 * l)) - 1
if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || mLen > m64 { //nolint:gosec // G114
mLen = m64 // The maximum lentgh on a 64bit arch
}
if mLen != uint64(int(mLen)) { //nolint:gosec // G114
return math.MaxInt32 - tagsize // We have only 32bit int's
}
return int(mLen) //nolint:gosec // G114
}
// MaxNonceLength returns the maximum nonce length for a given plaintext length.
// A return value <= 0 indicates that plaintext length is too large for
// any nonce length.
func MaxNonceLength(pdatalen int) int {
const tagsize = 16
for L := 2; L <= 8; L++ {
if maxlen(uint8(L), tagsize) >= pdatalen { //nolint:gosec // G115
return 15 - L
}
}
return 0
}
func (c *ccm) cbcRound(mac, data []byte) {
for i := 0; i < ccmBlockSize; i++ {
mac[i] ^= data[i]
}
c.b.Encrypt(mac, mac)
}
func (c *ccm) cbcData(mac, data []byte) {
for len(data) >= ccmBlockSize {
c.cbcRound(mac, data[:ccmBlockSize])
data = data[ccmBlockSize:]
}
if len(data) > 0 {
var block [ccmBlockSize]byte
copy(block[:], data)
c.cbcRound(mac, block[:])
}
}
var errPlaintextTooLong = errors.New("ccm: plaintext too large")
func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) {
var mac [ccmBlockSize]byte
if len(adata) > 0 {
mac[0] |= 1 << 6
}
mac[0] |= (c.M - 2) << 2
mac[0] |= c.L - 1
if len(nonce) != c.NonceSize() {
return nil, errInvalidNonceSize
}
if len(plaintext) > c.MaxLength() {
return nil, errPlaintextTooLong
}
binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext)))
copy(mac[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(mac[:], mac[:])
var block [ccmBlockSize]byte
if adataLength := uint64(len(adata)); adataLength > 0 { //nolint:nestif
// First adata block includes adata length
i := 2
if adataLength <= 0xfeff {
binary.BigEndian.PutUint16(block[:i], uint16(adataLength))
} else {
binary.BigEndian.PutUint16(block[0:2], 0xfeff)
if adataLength < uint64(1<<32) {
i = 2 + 4
binary.BigEndian.PutUint32(block[2:i], uint32(adataLength)) //nolint:gosec // G115
} else {
i = 2 + 8
binary.BigEndian.PutUint64(block[2:i], adataLength)
}
}
i = copy(block[i:], adata)
c.cbcRound(mac[:], block[:])
c.cbcData(mac[:], adata[i:])
}
if len(plaintext) > 0 {
c.cbcData(mac[:], plaintext)
}
return mac[:c.M], nil
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
// From crypto/cipher/gcm.go
// .
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
// The plaintext must be no longer than MaxLength() bytes long.
//
// The plaintext and dst may alias exactly or not at all.
func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte {
tag, err := c.tag(nonce, plaintext, adata)
if err != nil {
// The cipher.AEAD interface doesn't allow for an error return.
panic(err) // nolint
}
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
ret, out := sliceForAppend(dst, len(plaintext)+int(c.M))
stream.XORKeyStream(out, plaintext)
copy(out[len(plaintext):], tag)
return ret
}
var (
errOpen = errors.New("ccm: message authentication failed")
errCiphertextTooShort = errors.New("ccm: ciphertext too short")
errCiphertextTooLong = errors.New("ccm: ciphertext too long")
)
func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
if len(ciphertext) < int(c.M) {
return nil, errCiphertextTooShort
}
if len(ciphertext) > c.MaxLength()+c.Overhead() {
return nil, errCiphertextTooLong
}
tag := make([]byte, int(c.M))
copy(tag, ciphertext[len(ciphertext)-int(c.M):])
ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)]
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
// Cannot decrypt directly to dst since we're not supposed to
// reveal the plaintext to the caller if authentication fails.
plaintext := make([]byte, len(ciphertextWithoutTag))
stream.XORKeyStream(plaintext, ciphertextWithoutTag)
expectedTag, err := c.tag(nonce, plaintext, adata)
if err != nil {
return nil, err
}
if subtle.ConstantTimeCompare(tag, expectedTag) != 1 {
return nil, errOpen
}
return append(dst, plaintext...), nil
}

View file

@ -0,0 +1,128 @@
# Ciphersuite Package
This package provides DTLS cipher suite implementations for GCM, CCM, and CBC modes.
## Benchmarking
The package includes comprehensive benchmarks for all cipher operations across multiple payload sizes.
**Note:** Benchmarks are excluded from regular test runs using build tags. You must specify `-tags=bench` to run them.
### Running all ciphersuite benchmarks
```bash
go test -tags=bench -bench=. -benchmem
```
### Running a specific benchmark
- GCM benchmarks only:
```bash
go test -tags=bench -bench=BenchmarkGCM -benchmem
```
- GCM `Encrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkGCMEncrypt -benchmem
```
- GCM `Decrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkGCMDecrypt -benchmem
```
- CCM benchmarks only:
```bash
go test -tags=bench -bench=BenchmarkCCM -benchmem
```
- CCM `Encrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkCCMEncrypt -benchmem
```
- CCM `Decrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkCCMDecrypt -benchmem
```
- CBC benchmarks only:
```bash
go test -tags=bench -bench=BenchmarkCBC -benchmem
```
- CBC `Encrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkCBCEncrypt -benchmem
```
- CBC `Decrypt` benchmark only:
```bash
go test -tags=bench -bench=BenchmarkCBCDecrypt -benchmem
```
- All ciphers, with 1KB payloads only
```bash
go test -tags=bench -bench=/1KB -benchmem
```
- All ciphers, with 16B payloads only
```bash
go test -tags=bench -bench=/16B -benchmem
```
### Benchmark Options
Increase benchmark time for more accurate results:
```bash
go test -tags=bench -bench=BenchmarkGCM -benchmem -benchtime=5s
```
Run benchmarks multiple times:
```bash
go test -tags=bench -bench=BenchmarkGCM -benchmem -count=5
```
### Understanding Results
Example output:
```
BenchmarkGCMEncrypt/016B-8 5895367 202.6 ns/op 78.99 MB/s 160 B/op 5 allocs/op
```
- `5895367`: Number of iterations
- `202.6 ns/op`: Time per operation
- `78.99 MB/s`: Throughput
- `160 B/op`: Bytes allocated per operation
- `5 allocs/op`: Number of allocations per operation
## Profiling
Generate CPU profile:
```bash
go test -tags=bench -bench=BenchmarkGCMEncrypt -benchmem -cpuprofile=cpu.prof
go tool pprof -top cpu.prof
```
Generate memory profile:
```bash
go test -tags=bench -bench=BenchmarkGCMEncrypt -benchmem -memprofile=mem.prof
go tool pprof -top -alloc_objects mem.prof
```

View file

@ -0,0 +1,250 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import ( //nolint:gci
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"encoding/binary"
"hash"
"github.com/pion/dtls/v3/internal/util"
"github.com/pion/dtls/v3/pkg/crypto/prf"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
"golang.org/x/crypto/cryptobyte"
)
// block ciphers using cipher block chaining.
type cbcMode interface {
cipher.BlockMode
SetIV([]byte)
}
// CBC Provides an API to Encrypt/Decrypt DTLS 1.2 Packets.
type CBC struct {
writeCBC, readCBC cbcMode
writeMac, readMac []byte
h prf.HashFunc
}
// NewCBC creates a DTLS CBC Cipher.
func NewCBC(
localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, remoteMac []byte,
hashFunc prf.HashFunc,
) (*CBC, error) {
writeBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
readBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
writeCBC, ok := cipher.NewCBCEncrypter(writeBlock, localWriteIV).(cbcMode)
if !ok {
return nil, errFailedToCast
}
readCBC, ok := cipher.NewCBCDecrypter(readBlock, remoteWriteIV).(cbcMode)
if !ok {
return nil, errFailedToCast
}
return &CBC{
writeCBC: writeCBC,
writeMac: localMac,
readCBC: readCBC,
readMac: remoteMac,
h: hashFunc,
}, nil
}
// Encrypt encrypt a DTLS RecordLayer message.
func (c *CBC) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
payload := raw[pkt.Header.Size():]
raw = raw[:pkt.Header.Size()]
blockSize := c.writeCBC.BlockSize()
// Generate + Append MAC
h := pkt.Header
var err error
var mac []byte
if h.ContentType == protocol.ContentTypeConnectionID {
mac, err = c.hmacCID(h.Epoch, h.SequenceNumber, h.Version, payload, c.writeMac, c.h, h.ConnectionID)
} else {
mac, err = c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, payload, c.writeMac, c.h)
}
if err != nil {
return nil, err
}
payload = append(payload, mac...)
// Generate + Append padding
padding := make([]byte, blockSize-len(payload)%blockSize)
paddingLen := len(padding)
for i := 0; i < paddingLen; i++ {
padding[i] = byte(paddingLen - 1)
}
payload = append(payload, padding...)
// Generate IV
iv := make([]byte, blockSize)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
// Set IV + Encrypt + Prepend IV
c.writeCBC.SetIV(iv)
c.writeCBC.CryptBlocks(payload, payload)
payload = append(iv, payload...) //nolint:makezero // todo: FIX
// Prepend unencrypted header with encrypted payload
raw = append(raw, payload...)
// Update recordLayer size to include IV+MAC+Padding
binary.BigEndian.PutUint16(raw[pkt.Header.Size()-2:], uint16(len(raw)-pkt.Header.Size())) //nolint:gosec //G115
return raw, nil
}
// Decrypt decrypts a DTLS RecordLayer message.
func (c *CBC) Decrypt(header recordlayer.Header, in []byte) ([]byte, error) {
blockSize := c.readCBC.BlockSize()
mac := c.h()
if err := header.Unmarshal(in); err != nil {
return nil, err
}
body := in[header.Size():]
switch {
case header.ContentType == protocol.ContentTypeChangeCipherSpec:
// Nothing to encrypt with ChangeCipherSpec
return in, nil
case len(body)%blockSize != 0 || len(body) < blockSize+util.Max(mac.Size()+1, blockSize):
return nil, errNotEnoughRoomForNonce
}
// Set + remove per record IV
c.readCBC.SetIV(body[:blockSize])
body = body[blockSize:]
// Decrypt
c.readCBC.CryptBlocks(body, body)
// Padding+MAC needs to be checked in constant time
// Otherwise we reveal information about the level of correctness
paddingLen, paddingGood := examinePadding(body)
if paddingGood != 255 {
return nil, errInvalidMAC
}
macSize := mac.Size()
if len(body) < macSize {
return nil, errInvalidMAC
}
dataEnd := len(body) - macSize - paddingLen
expectedMAC := body[dataEnd : dataEnd+macSize]
var err error
var actualMAC []byte
if header.ContentType == protocol.ContentTypeConnectionID {
actualMAC, err = c.hmacCID(
header.Epoch, header.SequenceNumber, header.Version, body[:dataEnd], c.readMac, c.h, header.ConnectionID,
)
} else {
actualMAC, err = c.hmac(
header.Epoch, header.SequenceNumber, header.ContentType, header.Version, body[:dataEnd], c.readMac, c.h,
)
}
// Compute Local MAC and compare
if err != nil || !hmac.Equal(actualMAC, expectedMAC) {
return nil, errInvalidMAC
}
return append(in[:header.Size()], body[:dataEnd]...), nil
}
func (c *CBC) hmac(
epoch uint16,
sequenceNumber uint64,
contentType protocol.ContentType,
protocolVersion protocol.Version,
payload []byte,
key []byte,
hf func() hash.Hash,
) ([]byte, error) {
hmacHash := hmac.New(hf, key)
msg := make([]byte, 13)
binary.BigEndian.PutUint16(msg, epoch)
util.PutBigEndianUint48(msg[2:], sequenceNumber)
msg[8] = byte(contentType)
msg[9] = protocolVersion.Major
msg[10] = protocolVersion.Minor
binary.BigEndian.PutUint16(msg[11:], uint16(len(payload))) //nolint:gosec //G115
if _, err := hmacHash.Write(msg); err != nil {
return nil, err
}
if _, err := hmacHash.Write(payload); err != nil {
return nil, err
}
return hmacHash.Sum(nil), nil
}
// hmacCID calculates a MAC according to
// https://datatracker.ietf.org/doc/html/rfc9146#section-5.1
func (c *CBC) hmacCID(
epoch uint16,
sequenceNumber uint64,
protocolVersion protocol.Version,
payload []byte,
key []byte,
hf func() hash.Hash,
cid []byte,
) ([]byte, error) {
// Must unmarshal inner plaintext in orde to perform MAC.
ip := &recordlayer.InnerPlaintext{}
if err := ip.Unmarshal(payload); err != nil {
return nil, err
}
hmacHash := hmac.New(hf, key)
var msg cryptobyte.Builder
msg.AddUint64(seqNumPlaceholder)
msg.AddUint8(uint8(protocol.ContentTypeConnectionID))
msg.AddUint8(uint8(len(cid))) //nolint:gosec //G115
msg.AddUint8(uint8(protocol.ContentTypeConnectionID))
msg.AddUint8(protocolVersion.Major)
msg.AddUint8(protocolVersion.Minor)
msg.AddUint16(epoch)
util.AddUint48(&msg, sequenceNumber)
msg.AddBytes(cid)
msg.AddUint16(uint16(len(payload))) //nolint:gosec //G115
msg.AddBytes(ip.Content)
msg.AddUint8(uint8(ip.RealType))
msg.AddBytes(make([]byte, ip.Zeros))
if _, err := hmacHash.Write(msg.BytesOrPanic()); err != nil {
return nil, err
}
if _, err := hmacHash.Write(payload); err != nil {
return nil, err
}
return hmacHash.Sum(nil), nil
}

View file

@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/aes"
"github.com/pion/dtls/v3/pkg/crypto/ccm"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
// CCMTagLen is the length of Authentication Tag.
type CCMTagLen int
// CCM Enums.
const (
CCMTagLength8 CCMTagLen = 8
CCMTagLength CCMTagLen = 16
ccmNonceLength = 12
)
// CCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets.
type CCM struct {
aead *aead
}
// NewCCM creates a DTLS GCM Cipher.
func NewCCM(tagLen CCMTagLen, localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*CCM, error) {
localBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
localCCM, err := ccm.NewCCM(localBlock, int(tagLen), ccmNonceLength)
if err != nil {
return nil, err
}
remoteBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
remoteCCM, err := ccm.NewCCM(remoteBlock, int(tagLen), ccmNonceLength)
if err != nil {
return nil, err
}
return &CCM{
aead: newAEAD(
localCCM,
localWriteIV,
remoteCCM,
remoteWriteIV,
ccmNonceLength,
int(tagLen),
),
}, nil
}
// Encrypt encrypt a DTLS RecordLayer message.
func (c *CCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
return c.aead.encrypt(pkt, raw)
}
// Decrypt decrypts a DTLS RecordLayer message.
func (c *CCM) Decrypt(header recordlayer.Header, in []byte) ([]byte, error) {
return c.aead.decrypt(header, in)
}

View file

@ -0,0 +1,226 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package ciphersuite provides the crypto operations needed for a DTLS CipherSuite
package ciphersuite
import (
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"sync"
"github.com/pion/dtls/v3/internal/util"
"github.com/pion/dtls/v3/pkg/protocol"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
"golang.org/x/crypto/cryptobyte"
)
const (
// 8 bytes of 0xff.
// https://datatracker.ietf.org/doc/html/rfc9146#name-record-payload-protection
seqNumPlaceholder = 0xffffffffffffffff
)
var (
//nolint:err113
errNotEnoughRoomForNonce = &protocol.InternalError{Err: errors.New("buffer not long enough to contain nonce")}
//nolint:err113
errDecryptPacket = &protocol.TemporaryError{Err: errors.New("failed to decrypt packet")}
//nolint:err113
errInvalidMAC = &protocol.TemporaryError{Err: errors.New("invalid mac")}
//nolint:err113
errFailedToCast = &protocol.FatalError{Err: errors.New("failed to cast")}
)
// aead provides a generic API to Encrypt/Decrypt DTLS 1.2 Packets.
type aead struct {
localAEAD cipher.AEAD
remoteAEAD cipher.AEAD
localWriteIV []byte
remoteWriteIV []byte
nonceLength int
tagLength int
// buffer pool for (fixed-size) nonces.
nonceBufferPool sync.Pool
}
// newAEAD creates a generic DTLS AEAD-based Cipher.
func newAEAD(
localAEAD cipher.AEAD,
localWriteIV []byte,
remoteAEAD cipher.AEAD,
remoteWriteIV []byte,
nonceLength int,
tagLength int,
) *aead {
return &aead{
localAEAD: localAEAD,
localWriteIV: localWriteIV,
remoteAEAD: remoteAEAD,
remoteWriteIV: remoteWriteIV,
nonceLength: nonceLength,
tagLength: tagLength,
nonceBufferPool: sync.Pool{
New: func() any {
b := make([]byte, nonceLength)
return &b // nolint:nlreturn
},
},
}
}
// encrypt encrypts a DTLS RecordLayer message.
func (a *aead) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
payload := raw[pkt.Header.Size():]
raw = raw[:pkt.Header.Size()]
// Get nonce buffer from pool
noncePtr := a.nonceBufferPool.Get().(*[]byte) // nolint:forcetypeassert
nonce := *noncePtr
copy(nonce, a.localWriteIV[:4])
// https://www.rfc-editor.org/rfc/rfc9325#name-nonce-reuse-in-tls-12
seq64 := (uint64(pkt.Header.Epoch) << 48) | (pkt.Header.SequenceNumber & 0x0000ffffffffffff)
binary.BigEndian.PutUint64(nonce[4:], seq64)
var additionalData []byte
if pkt.Header.ContentType == protocol.ContentTypeConnectionID {
additionalData = generateAEADAdditionalDataCID(&pkt.Header, len(payload))
} else {
additionalData = generateAEADAdditionalData(&pkt.Header, len(payload))
}
finalSize := len(raw) + 8 + len(payload) + a.tagLength
r := make([]byte, finalSize)
copy(r, raw)
copy(r[len(raw):], nonce[4:])
a.localAEAD.Seal(r[len(raw)+8:len(raw)+8], nonce, payload, additionalData)
// Update recordLayer size to include explicit nonce
binary.BigEndian.PutUint16(r[pkt.Header.Size()-2:], uint16(len(r)-pkt.Header.Size())) //nolint:gosec //G115
// Return nonce buffer to pool
a.nonceBufferPool.Put(noncePtr)
return r, nil
}
// decrypt decrypts a DTLS RecordLayer message.
func (a *aead) decrypt(header recordlayer.Header, in []byte) ([]byte, error) {
err := header.Unmarshal(in)
switch {
case err != nil:
return nil, err
case header.ContentType == protocol.ContentTypeChangeCipherSpec:
// Nothing to encrypt with ChangeCipherSpec
return in, nil
case len(in) <= (8 + header.Size()):
return nil, errNotEnoughRoomForNonce
}
// Get nonce buffer from pool
noncePtr := a.nonceBufferPool.Get().(*[]byte) // nolint:forcetypeassert
nonce := *noncePtr
copy(nonce[:4], a.remoteWriteIV[:4])
copy(nonce[4:], in[header.Size():header.Size()+8])
out := in[header.Size()+8:]
var additionalData []byte
if header.ContentType == protocol.ContentTypeConnectionID {
additionalData = generateAEADAdditionalDataCID(&header, len(out)-a.tagLength)
} else {
additionalData = generateAEADAdditionalData(&header, len(out)-a.tagLength)
}
out, err = a.remoteAEAD.Open(out[:0], nonce, out, additionalData)
if err != nil {
// Return nonce buffer to pool
a.nonceBufferPool.Put(noncePtr)
return nil, fmt.Errorf("%w: %v", errDecryptPacket, err) //nolint:errorlint
}
// Return nonce buffer to pool
a.nonceBufferPool.Put(noncePtr)
return append(in[:header.Size()], out...), nil
}
func generateAEADAdditionalData(h *recordlayer.Header, payloadLen int) []byte {
var additionalData [13]byte
// SequenceNumber MUST be set first
// we only want uint48, clobbering an extra 2 (using uint64, Golang doesn't have uint48)
binary.BigEndian.PutUint64(additionalData[:], h.SequenceNumber)
binary.BigEndian.PutUint16(additionalData[:], h.Epoch)
additionalData[8] = byte(h.ContentType)
additionalData[9] = h.Version.Major
additionalData[10] = h.Version.Minor
//nolint:gosec //G115
binary.BigEndian.PutUint16(additionalData[len(additionalData)-2:], uint16(payloadLen))
return additionalData[:]
}
// generateAEADAdditionalDataCID generates additional data for AEAD ciphers
// according to https://datatracker.ietf.org/doc/html/rfc9146#name-aead-ciphers
func generateAEADAdditionalDataCID(h *recordlayer.Header, payloadLen int) []byte {
var builder cryptobyte.Builder
builder.AddUint64(seqNumPlaceholder)
builder.AddUint8(uint8(protocol.ContentTypeConnectionID))
builder.AddUint8(uint8(len(h.ConnectionID))) //nolint:gosec //G115
builder.AddUint8(uint8(protocol.ContentTypeConnectionID))
builder.AddUint8(h.Version.Major)
builder.AddUint8(h.Version.Minor)
builder.AddUint16(h.Epoch)
util.AddUint48(&builder, h.SequenceNumber)
builder.AddBytes(h.ConnectionID)
builder.AddUint16(uint16(payloadLen)) //nolint:gosec //G115
return builder.BytesOrPanic()
}
// examinePadding returns, in constant time, the length of the padding to remove
// from the end of payload. It also returns a byte which is equal to 255 if the
// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
//
// https://github.com/golang/go/blob/039c2081d1178f90a8fa2f4e6958693129f8de33/src/crypto/tls/conn.go#L245
func examinePadding(payload []byte) (toRemove int, good byte) {
if len(payload) < 1 {
return 0, 0
}
paddingLen := payload[len(payload)-1]
t := uint(len(payload)-1) - uint(paddingLen) //nolint:gosec //G115
// if len(payload) >= (paddingLen - 1) then the MSB of t is zero
good = byte(int32(^t) >> 31) //nolint:gosec //G115
// The maximum possible padding length plus the actual length field
toCheck := min(
// The length of the padded data is public, so we can use an if here
256, len(payload))
for i := 0; i < toCheck; i++ {
t := uint(paddingLen) - uint(i) //nolint:gosec //G115
// if i <= paddingLen then the MSB of t is zero
mask := byte(int32(^t) >> 31) //nolint:gosec //G115
b := payload[len(payload)-1-i]
good &^= mask&paddingLen ^ mask&b
}
// We AND together the bits of good and replicate the result across
// all the bits.
good &= good << 4
good &= good << 2
good &= good << 1
good = uint8(int8(good) >> 7) //nolint:gosec //G115
toRemove = int(paddingLen) + 1
return toRemove, good
}

View file

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ciphersuite
import (
"crypto/aes"
"crypto/cipher"
"github.com/pion/dtls/v3/pkg/protocol/recordlayer"
)
const (
gcmTagLength = 16
gcmNonceLength = 12
)
// GCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets.
type GCM struct {
aead *aead
}
// NewGCM creates a DTLS GCM Cipher.
func NewGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*GCM, error) {
localBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
localGCM, err := cipher.NewGCM(localBlock)
if err != nil {
return nil, err
}
remoteBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
remoteGCM, err := cipher.NewGCM(remoteBlock)
if err != nil {
return nil, err
}
return &GCM{
aead: newAEAD(
localGCM,
localWriteIV,
remoteGCM,
remoteWriteIV,
gcmNonceLength,
gcmTagLength,
),
}, nil
}
// Encrypt encrypts a DTLS RecordLayer message.
func (g *GCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
return g.aead.encrypt(pkt, raw)
}
// Decrypt decrypts a DTLS RecordLayer message.
func (g *GCM) Decrypt(header recordlayer.Header, in []byte) ([]byte, error) {
return g.aead.decrypt(header, in)
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package clientcertificate provides all the support Client Certificate types
package clientcertificate
// Type is used to communicate what
// type of certificate is being transported
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-2
type Type byte
// ClientCertificateType enums.
const (
RSASign Type = 1
ECDSASign Type = 64
)
// Types returns all valid ClientCertificate Types.
func Types() map[Type]bool {
return map[Type]bool{
RSASign: true,
ECDSASign: true,
}
}

View file

@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package elliptic provides elliptic curve cryptography for DTLS
package elliptic
import (
"crypto/ecdh"
"crypto/rand"
"errors"
"fmt"
)
var errInvalidNamedCurve = errors.New("invalid named curve")
// CurvePointFormat is used to represent the IANA registered curve points
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-9
type CurvePointFormat byte
// CurvePointFormat enums.
const (
CurvePointFormatUncompressed CurvePointFormat = 0
)
// Keypair is a Curve with a Private/Public Keypair.
type Keypair struct {
Curve Curve
PublicKey []byte
PrivateKey []byte
}
// CurveType is used to represent the IANA registered curve types for TLS
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10
type CurveType byte
// CurveType enums.
const (
CurveTypeNamedCurve CurveType = 0x03
)
// CurveTypes returns all known curves.
func CurveTypes() map[CurveType]struct{} {
return map[CurveType]struct{}{
CurveTypeNamedCurve: {},
}
}
// Curve is used to represent the IANA registered curves for TLS
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
type Curve uint16
// Curve enums.
const (
P256 Curve = 0x0017
P384 Curve = 0x0018
X25519 Curve = 0x001d
// X25519MLKEM768
// https://pkg.go.dev/crypto/internal/fips140/mlkem
// https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/
// https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/
)
func (c Curve) String() string {
switch c {
case P256:
return "P-256"
case P384:
return "P-384"
case X25519:
return "X25519"
}
return fmt.Sprintf("%#x", uint16(c))
}
// Curves returns all curves we implement.
func Curves() map[Curve]bool {
return map[Curve]bool{
X25519: true,
P256: true,
P384: true,
}
}
// GenerateKeypair generates a keypair for the given Curve.
func GenerateKeypair(curve Curve) (*Keypair, error) {
ec, err := curve.toECDH()
if err != nil {
return nil, err
}
sk, err := ec.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
pk := sk.PublicKey()
return &Keypair{
Curve: curve,
PublicKey: pk.Bytes(), // NIST: SEC1 uncompressed (04||X||Y); X25519: 32 bytes
PrivateKey: sk.Bytes(), // Scalar suitable for ecdh.NewPrivateKey
}, nil
}
// toECDH returns the crypto/ecdh curve for our enum.
func (c Curve) toECDH() (ecdh.Curve, error) {
switch c {
case X25519:
return ecdh.X25519(), nil
case P256:
return ecdh.P256(), nil
case P384:
return ecdh.P384(), nil
default:
return nil, errInvalidNamedCurve
}
}

View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package fingerprint provides a helper to create fingerprint string from certificate
package fingerprint
import (
"crypto"
"crypto/x509"
"errors"
"fmt"
)
var (
errHashUnavailable = errors.New("fingerprint: hash algorithm is not linked into the binary")
errInvalidFingerprintLength = errors.New("fingerprint: invalid fingerprint length")
)
// Fingerprint creates a fingerprint for a certificate using the specified hash algorithm.
func Fingerprint(cert *x509.Certificate, algo crypto.Hash) (string, error) {
if !algo.Available() {
return "", errHashUnavailable
}
h := algo.New()
for i := 0; i < len(cert.Raw); {
n, _ := h.Write(cert.Raw[i:])
// Hash.Writer is specified to be never returning an error.
// https://golang.org/pkg/hash/#Hash
i += n
}
digest := fmt.Appendf(nil, "%x", h.Sum(nil))
digestlen := len(digest)
if digestlen == 0 {
return "", nil
}
if digestlen%2 != 0 {
return "", errInvalidFingerprintLength
}
res := make([]byte, digestlen>>1+digestlen-1)
pos := 0
for i, c := range digest {
res[pos] = c
pos++
if (i)%2 != 0 && i < digestlen-1 {
res[pos] = byte(':')
pos++
}
}
return string(res), nil
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package fingerprint
import (
"crypto"
"errors"
"strings"
)
var errInvalidHashAlgorithm = errors.New("fingerprint: invalid hash algorithm")
func nameToHash() map[string]crypto.Hash {
return map[string]crypto.Hash{
"md5": crypto.MD5, // [RFC3279]
"sha-1": crypto.SHA1, // [RFC3279]
"sha-224": crypto.SHA224, // [RFC4055]
"sha-256": crypto.SHA256, // [RFC4055]
"sha-384": crypto.SHA384, // [RFC4055]
"sha-512": crypto.SHA512, // [RFC4055]
}
}
// HashFromString allows looking up a hash algorithm by it's string representation.
func HashFromString(s string) (crypto.Hash, error) {
if h, ok := nameToHash()[strings.ToLower(s)]; ok {
return h, nil
}
return 0, errInvalidHashAlgorithm
}
// StringFromHash allows looking up a string representation of the crypto.Hash.
func StringFromHash(hash crypto.Hash) (string, error) {
for s, h := range nameToHash() {
if h == hash {
return s, nil
}
}
return "", errInvalidHashAlgorithm
}

157
vendor/github.com/pion/dtls/v3/pkg/crypto/hash/hash.go generated vendored Normal file
View file

@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package hash provides TLS HashAlgorithm as defined in TLS 1.2
package hash
import ( //nolint:gci
"crypto"
"crypto/md5" //nolint:gosec
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
)
// Algorithm is used to indicate the hash algorithm used
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
type Algorithm uint16
// Supported hash algorithms.
const (
None Algorithm = 0 // Blacklisted
MD5 Algorithm = 1 // Blacklisted
SHA1 Algorithm = 2 // Blacklisted
SHA224 Algorithm = 3
SHA256 Algorithm = 4
SHA384 Algorithm = 5
SHA512 Algorithm = 6
Ed25519 Algorithm = 8
)
// String makes hashAlgorithm printable.
func (a Algorithm) String() string {
switch a {
case None:
return "none"
case MD5:
return "md5" // [RFC3279]
case SHA1:
return "sha-1" // [RFC3279]
case SHA224:
return "sha-224" // [RFC4055]
case SHA256:
return "sha-256" // [RFC4055]
case SHA384:
return "sha-384" // [RFC4055]
case SHA512:
return "sha-512" // [RFC4055]
case Ed25519:
return "null"
default:
return "unknown or unsupported hash algorithm"
}
}
// Digest performs a digest on the passed value.
func (a Algorithm) Digest(b []byte) []byte {
switch a {
case None:
return nil
case MD5:
hash := md5.Sum(b) // #nosec
return hash[:]
case SHA1:
hash := sha1.Sum(b) // #nosec
return hash[:]
case SHA224:
hash := sha256.Sum224(b)
return hash[:]
case SHA256:
hash := sha256.Sum256(b)
return hash[:]
case SHA384:
hash := sha512.Sum384(b)
return hash[:]
case SHA512:
hash := sha512.Sum512(b)
return hash[:]
default:
return nil
}
}
// Insecure returns if the given HashAlgorithm is considered secure in DTLS 1.2
// .
func (a Algorithm) Insecure() bool {
switch a {
case None, MD5, SHA1:
return true
default:
return false
}
}
// CryptoHash returns the crypto.Hash implementation for the given HashAlgorithm.
func (a Algorithm) CryptoHash() crypto.Hash {
switch a {
case None:
return crypto.Hash(0)
case MD5:
return crypto.MD5
case SHA1:
return crypto.SHA1
case SHA224:
return crypto.SHA224
case SHA256:
return crypto.SHA256
case SHA384:
return crypto.SHA384
case SHA512:
return crypto.SHA512
case Ed25519:
return crypto.Hash(0)
default:
return crypto.Hash(0)
}
}
// Algorithms returns all the supported Hash Algorithms.
func Algorithms() map[Algorithm]struct{} {
return map[Algorithm]struct{}{
None: {},
MD5: {},
SHA1: {},
SHA224: {},
SHA256: {},
SHA384: {},
SHA512: {},
Ed25519: {},
}
}
// ExtractHashFromPSS extracts the hash algorithm from an RSA-PSS SignatureScheme value.
// This handles TLS 1.3 PSS schemes.
// Returns None if the scheme is not a recognized PSS scheme.
func ExtractHashFromPSS(pssScheme uint16) Algorithm {
// Note: We can't import signature package here due to circular dependency,
// so we use the raw values. These correspond to:
// 0x0804 = RSA_PSS_RSAE_SHA256, 0x0809 = RSA_PSS_PSS_SHA256
// 0x0805 = RSA_PSS_RSAE_SHA384, 0x080a = RSA_PSS_PSS_SHA384
// 0x0806 = RSA_PSS_RSAE_SHA512, 0x080b = RSA_PSS_PSS_SHA512
switch pssScheme {
case 0x0804, 0x0809:
return SHA256
case 0x0805, 0x080a:
return SHA384
case 0x0806, 0x080b:
return SHA512
default:
return None
}
}

264
vendor/github.com/pion/dtls/v3/pkg/crypto/prf/prf.go generated vendored Normal file
View file

@ -0,0 +1,264 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package prf implements TLS 1.2 Pseudorandom functions
package prf
import ( //nolint:gci
"crypto/ecdh"
"crypto/hmac"
"encoding/binary"
"errors"
"fmt"
"hash"
"math"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol"
)
const (
masterSecretLabel = "master secret"
extendedMasterSecretLabel = "extended master secret"
keyExpansionLabel = "key expansion"
verifyDataClientLabel = "client finished"
verifyDataServerLabel = "server finished"
)
// HashFunc allows callers to decide what hash is used in PRF.
type HashFunc func() hash.Hash
// EncryptionKeys is all the state needed for a TLS CipherSuite.
type EncryptionKeys struct {
MasterSecret []byte
ClientMACKey []byte
ServerMACKey []byte
ClientWriteKey []byte
ServerWriteKey []byte
ClientWriteIV []byte
ServerWriteIV []byte
}
var errInvalidNamedCurve = &protocol.FatalError{Err: errors.New("invalid named curve")} //nolint:err113
func (e *EncryptionKeys) String() string {
return fmt.Sprintf(`encryptionKeys:
- masterSecret: %#v
- clientMACKey: %#v
- serverMACKey: %#v
- clientWriteKey: %#v
- serverWriteKey: %#v
- clientWriteIV: %#v
- serverWriteIV: %#v
`,
e.MasterSecret,
e.ClientMACKey,
e.ServerMACKey,
e.ClientWriteKey,
e.ServerWriteKey,
e.ClientWriteIV,
e.ServerWriteIV)
}
// PSKPreMasterSecret generates the PSK Premaster Secret
// The premaster secret is formed as follows: if the PSK is N octets
// long, concatenate a uint16 with the value N, N zero octets, a second
// uint16 with the value N, and the PSK itself.
//
// https://tools.ietf.org/html/rfc4279#section-2
func PSKPreMasterSecret(psk []byte) []byte {
pskLen := uint16(len(psk)) //nolint:gosec // G115
out := append(make([]byte, 2+pskLen+2), psk...)
binary.BigEndian.PutUint16(out, pskLen)
binary.BigEndian.PutUint16(out[2+pskLen:], pskLen)
return out
}
// EcdhePSKPreMasterSecret implements TLS 1.2 Premaster Secret generation given a psk, a keypair and a curve
//
// https://datatracker.ietf.org/doc/html/rfc5489#section-2
func EcdhePSKPreMasterSecret(psk, publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) {
preMasterSecret, err := PreMasterSecret(publicKey, privateKey, curve)
if err != nil {
return nil, err
}
out := make([]byte, 2+len(preMasterSecret)+2+len(psk))
// write preMasterSecret length
offset := 0
binary.BigEndian.PutUint16(out[offset:], uint16(len(preMasterSecret))) //nolint:gosec // G115
offset += 2
// write preMasterSecret
copy(out[offset:], preMasterSecret)
offset += len(preMasterSecret)
// write psk length
binary.BigEndian.PutUint16(out[offset:], uint16(len(psk))) //nolint:gosec // G115
offset += 2
// write psk
copy(out[offset:], psk)
return out, nil
}
// PreMasterSecret implements TLS 1.2 Premaster Secret generation given a keypair and a curve.
func PreMasterSecret(publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) {
var ec ecdh.Curve
switch curve {
case elliptic.X25519:
ec = ecdh.X25519()
case elliptic.P256:
ec = ecdh.P256()
case elliptic.P384:
ec = ecdh.P384()
default:
return nil, errInvalidNamedCurve
}
sk, err := ec.NewPrivateKey(privateKey)
if err != nil {
return nil, err
}
pk, err := ec.NewPublicKey(publicKey) // NIST: SEC1 uncompressed; X25519: 32-byte u
if err != nil {
return nil, err
}
return sk.ECDH(pk)
}
// PHash is PRF is the SHA-256 hash function is used for all cipher suites
// defined in this TLS 1.2 document and in TLS documents published prior to this
// document when TLS 1.2 is negotiated. New cipher suites MUST explicitly
// specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
// stronger standard hash function.
//
// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
// HMAC_hash(secret, A(2) + seed) +
// HMAC_hash(secret, A(3) + seed) + ...
//
// A() is defined as:
//
// A(0) = seed
// A(i) = HMAC_hash(secret, A(i-1))
//
// P_hash can be iterated as many times as necessary to produce the
// required quantity of data. For example, if P_SHA256 is being used to
// create 80 bytes of data, it will have to be iterated three times
// (through A(3)), creating 96 bytes of output data; the last 16 bytes
// of the final iteration will then be discarded, leaving 80 bytes of
// output data.
//
// https://tools.ietf.org/html/rfc4346w
func PHash(secret, seed []byte, requestedLength int, hashFunc HashFunc) ([]byte, error) {
hmacSHA256 := func(key, data []byte) ([]byte, error) {
mac := hmac.New(hashFunc, key)
if _, err := mac.Write(data); err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
var err error
lastRound := seed
out := []byte{}
iterations := int(math.Ceil(float64(requestedLength) / float64(hashFunc().Size())))
for i := 0; i < iterations; i++ {
lastRound, err = hmacSHA256(secret, lastRound)
if err != nil {
return nil, err
}
withSecret, err := hmacSHA256(secret, append(lastRound, seed...))
if err != nil {
return nil, err
}
out = append(out, withSecret...)
}
return out[:requestedLength], nil
}
// ExtendedMasterSecret generates a Extended MasterSecret as defined in
// https://tools.ietf.org/html/rfc7627
func ExtendedMasterSecret(preMasterSecret, sessionHash []byte, h HashFunc) ([]byte, error) {
seed := append([]byte(extendedMasterSecretLabel), sessionHash...)
return PHash(preMasterSecret, seed, 48, h)
}
// MasterSecret generates a TLS 1.2 MasterSecret.
func MasterSecret(preMasterSecret, clientRandom, serverRandom []byte, h HashFunc) ([]byte, error) {
seed := append(append([]byte(masterSecretLabel), clientRandom...), serverRandom...)
return PHash(preMasterSecret, seed, 48, h)
}
// GenerateEncryptionKeys is the final step TLS 1.2 PRF. Given all state generated so far generates
// the final keys need for encryption.
func GenerateEncryptionKeys(
masterSecret, clientRandom, serverRandom []byte,
macLen, keyLen, ivLen int,
h HashFunc,
) (*EncryptionKeys, error) {
seed := append(append([]byte(keyExpansionLabel), serverRandom...), clientRandom...)
keyMaterial, err := PHash(masterSecret, seed, (2*macLen)+(2*keyLen)+(2*ivLen), h)
if err != nil {
return nil, err
}
clientMACKey := keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
serverMACKey := keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
clientWriteKey := keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
serverWriteKey := keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
clientWriteIV := keyMaterial[:ivLen]
keyMaterial = keyMaterial[ivLen:]
serverWriteIV := keyMaterial[:ivLen]
return &EncryptionKeys{
MasterSecret: masterSecret,
ClientMACKey: clientMACKey,
ServerMACKey: serverMACKey,
ClientWriteKey: clientWriteKey,
ServerWriteKey: serverWriteKey,
ClientWriteIV: clientWriteIV,
ServerWriteIV: serverWriteIV,
}, nil
}
func prfVerifyData(masterSecret, handshakeBodies []byte, label string, hashFunc HashFunc) ([]byte, error) {
h := hashFunc()
if _, err := h.Write(handshakeBodies); err != nil {
return nil, err
}
seed := append([]byte(label), h.Sum(nil)...)
return PHash(masterSecret, seed, 12, hashFunc)
}
// VerifyDataClient is caled on the Client Side to either verify or generate the VerifyData message.
func VerifyDataClient(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
return prfVerifyData(masterSecret, handshakeBodies, verifyDataClientLabel, h)
}
// VerifyDataServer is caled on the Server Side to either verify or generate the VerifyData message.
func VerifyDataServer(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
return prfVerifyData(masterSecret, handshakeBodies, verifyDataServerLabel, h)
}

View file

@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package signature provides our implemented Signature Algorithms
package signature
import "github.com/pion/dtls/v3/pkg/crypto/hash"
// Algorithm as defined in TLS 1.2
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
type Algorithm uint16
// SignatureAlgorithm enums.
const (
Anonymous Algorithm = 0
RSA Algorithm = 1
ECDSA Algorithm = 3
Ed25519 Algorithm = 7
// RSA-PSS (DTLS 1.3 only) - full SignatureScheme values
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme
RSA_PSS_RSAE_SHA256 Algorithm = 0x0804 // nolint: staticcheck
RSA_PSS_RSAE_SHA384 Algorithm = 0x0805 // nolint: staticcheck
RSA_PSS_RSAE_SHA512 Algorithm = 0x0806 // nolint: revive,staticcheck
RSA_PSS_PSS_SHA256 Algorithm = 0x0809 // nolint: revive,staticcheck
RSA_PSS_PSS_SHA384 Algorithm = 0x080a // nolint: revive,staticcheck
RSA_PSS_PSS_SHA512 Algorithm = 0x080b // nolint: revive,staticcheck
)
// Algorithms returns all implemented Signature Algorithms.
func Algorithms() map[Algorithm]struct{} {
return map[Algorithm]struct{}{
Anonymous: {},
RSA: {},
ECDSA: {},
Ed25519: {},
RSA_PSS_RSAE_SHA256: {},
RSA_PSS_RSAE_SHA384: {},
RSA_PSS_RSAE_SHA512: {},
RSA_PSS_PSS_SHA256: {},
RSA_PSS_PSS_SHA384: {},
RSA_PSS_PSS_SHA512: {},
}
}
// IsPSS returns true if the algorithm is an RSA-PSS signature scheme.
// It's tempting to check for range between 0x0804 and 0x080b, but 0x0807 is Ed25519
// and 0x0808 is Ed448, which are NOT PSS, so we check specific values instead.
func (a Algorithm) IsPSS() bool {
return a == RSA_PSS_RSAE_SHA256 ||
a == RSA_PSS_RSAE_SHA384 ||
a == RSA_PSS_RSAE_SHA512 ||
a == RSA_PSS_PSS_SHA256 ||
a == RSA_PSS_PSS_SHA384 ||
a == RSA_PSS_PSS_SHA512
}
// IsUnsupported returns true if the algorithm is a signature scheme that is
// not supported by pion/dtls.
func (a Algorithm) IsUnsupported() bool {
// Skip RSA_PSS_PSS schemes (0x0809-0x080b). We parse them for interoperability
// but don't negotiate them to avoid unnecessary complexity for certificates that
// don't exist in practice. This follows the pragmatic approach of Go's crypto/tls
// and BoringSSL: target real-world WebPKI use cases rather than RFC completeness.
return a == RSA_PSS_PSS_SHA256 ||
a == RSA_PSS_PSS_SHA384 ||
a == RSA_PSS_PSS_SHA512
}
// GetPSSHash returns the hash algorithm associated with an RSA-PSS signature scheme.
// Returns hash.None if the algorithm is not an RSA-PSS scheme.
func (a Algorithm) GetPSSHash() hash.Algorithm {
switch a {
case RSA_PSS_RSAE_SHA256, RSA_PSS_PSS_SHA256:
return hash.SHA256
case RSA_PSS_RSAE_SHA384, RSA_PSS_PSS_SHA384:
return hash.SHA384
case RSA_PSS_RSAE_SHA512, RSA_PSS_PSS_SHA512:
return hash.SHA512
default:
return hash.None
}
}

View file

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package signaturehash
import "errors"
var (
errNoAvailableSignatureSchemes = errors.New("connection can not be created, no SignatureScheme satisfy this Config")
errInvalidSignatureAlgorithm = errors.New("invalid signature algorithm")
errInvalidHashAlgorithm = errors.New("invalid hash algorithm")
errInvalidPrivateKey = errors.New("invalid private key type")
)

View file

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package signaturehash
import (
"crypto"
)
// selectSignatureScheme13 returns most preferred and compatible scheme.
// It's compatible with all DTLS versions up to and including 1.3.
func selectSignatureScheme13(sigs []Algorithm, privateKey crypto.PrivateKey, is13 bool) (Algorithm, error) {
signer, ok := privateKey.(crypto.Signer)
if !ok {
return Algorithm{}, errInvalidPrivateKey
}
for _, ss := range sigs {
// Skip PSS schemes for DTLS 1.2 (PSS is only supported in DTLS 1.3)
if !is13 && ss.Signature.IsPSS() {
continue
}
// Skip schemes understood but not supported by pion/dtls.
if ss.Signature.IsUnsupported() {
continue
}
if ss.isCompatible(signer) {
return ss, nil
}
}
return Algorithm{}, errNoAvailableSignatureSchemes
}

View file

@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package signaturehash provides the SignatureHashAlgorithm as defined in TLS 1.2
package signaturehash
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/pion/dtls/v3/pkg/crypto/hash"
"github.com/pion/dtls/v3/pkg/crypto/signature"
)
// Algorithm is a signature/hash algorithm pairs which may be used in
// digital signatures.
//
// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
type Algorithm struct {
Hash hash.Algorithm
Signature signature.Algorithm
}
// Algorithms returns signature algorithms compatible with DTLS 1.2 / TLS 1.2.
// This excludes TLS 1.3-specific schemes like RSA-PSS to ensure compatibility
// with implementations like OpenSSL that don't recognize TLS 1.3 signature
// algorithm IDs in DTLS 1.2 handshakes.
//
// IMPORTANT: order in this slice determines priority used by SelectSignatureScheme.
//
// Order follows industry standard preference (ECDSA-first) as used by OpenSSL,
// BoringSSL, Firefox, Chrome, and other major TLS implementations.
func Algorithms() []Algorithm {
return []Algorithm{
// ECDSA schemes (modern, efficient - industry standard preference)
{hash.SHA256, signature.ECDSA},
{hash.SHA384, signature.ECDSA},
{hash.SHA512, signature.ECDSA},
// Ed25519
{hash.Ed25519, signature.Ed25519},
// RSA PKCS#1 v1.5 schemes (legacy, DTLS 1.2)
{hash.SHA256, signature.RSA},
{hash.SHA384, signature.RSA},
{hash.SHA512, signature.RSA},
}
}
// SelectSignatureScheme returns most preferred and compatible scheme for DTLS <= 1.2.
func SelectSignatureScheme(sigs []Algorithm, privateKey crypto.PrivateKey) (Algorithm, error) {
return selectSignatureScheme13(sigs, privateKey, false)
}
// SelectSignatureScheme13 returns most preferred and compatible scheme for DTLS 1.3.
func SelectSignatureScheme13(sigs []Algorithm, privateKey crypto.PrivateKey) (Algorithm, error) {
return selectSignatureScheme13(sigs, privateKey, true)
}
// isCompatible checks that given private key is compatible with the signature scheme.
func (a *Algorithm) isCompatible(signer crypto.Signer) bool {
switch signer.Public().(type) {
case ed25519.PublicKey:
return a.Signature == signature.Ed25519
case *ecdsa.PublicKey:
return a.Signature == signature.ECDSA
case *rsa.PublicKey:
// RSA keys are compatible with both PKCS#1 v1.5 and PSS signatures
return a.Signature == signature.RSA || a.Signature.IsPSS()
default:
return false
}
}
// ParseSignatureSchemes translates []tls.SignatureScheme to []signatureHashAlgorithm.
// It returns default signature scheme list if no SignatureScheme is passed.
// This function handles both TLS 1.2 byte-split encoding and TLS 1.3 PSS full uint16 schemes.
//
// For DTLS 1.2 / TLS 1.2, this returns Algorithms() which excludes TLS 1.3-specific
// schemes like RSA-PSS for compatibility with implementations like OpenSSL.
// When DTLS 1.3 is implemented, use Algorithms13() or create ParseSignatureSchemes13().
func ParseSignatureSchemes(sigs []tls.SignatureScheme, insecureHashes bool) ([]Algorithm, error) {
if len(sigs) == 0 {
return Algorithms(), nil
}
out := []Algorithm{}
for _, ss := range sigs {
hashAlg, sigAlg, err := parseSignatureScheme(ss)
if err != nil {
return nil, err
}
if hashAlg.Insecure() && !insecureHashes {
continue
}
out = append(out, Algorithm{
Hash: hashAlg,
Signature: sigAlg,
})
}
if len(out) == 0 {
return nil, errNoAvailableSignatureSchemes
}
return out, nil
}
// FromCertificate maps x509.SignatureAlgorithm to the corresponding Algorithm type.
func FromCertificate(cert *x509.Certificate) (Algorithm, error) { //nolint:cyclop
var hashAlg hash.Algorithm
var sigAlg signature.Algorithm
switch cert.SignatureAlgorithm {
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS:
hashAlg = hash.SHA256
sigAlg = signature.RSA
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS:
hashAlg = hash.SHA384
sigAlg = signature.RSA
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS:
hashAlg = hash.SHA512
sigAlg = signature.RSA
case x509.ECDSAWithSHA256:
hashAlg = hash.SHA256
sigAlg = signature.ECDSA
case x509.ECDSAWithSHA384:
hashAlg = hash.SHA384
sigAlg = signature.ECDSA
case x509.ECDSAWithSHA512:
hashAlg = hash.SHA512
sigAlg = signature.ECDSA
case x509.PureEd25519:
hashAlg = hash.None // Ed25519 doesn't use a separate hash
sigAlg = signature.Ed25519
case x509.SHA1WithRSA:
hashAlg = hash.SHA1
sigAlg = signature.RSA
case x509.ECDSAWithSHA1:
hashAlg = hash.SHA1
sigAlg = signature.ECDSA
default:
return Algorithm{}, errInvalidSignatureAlgorithm
}
return Algorithm{Hash: hashAlg, Signature: sigAlg}, nil
}
// parseSignatureScheme translates a tls.SignatureScheme to a hash.Algorithm
// and signature.Algorithm. It returns default signature scheme list if no
// SignatureScheme is passed. This function handles both TLS 1.2 byte-split
// encoding and TLS 1.3 PSS full uint16 schemes.
func parseSignatureScheme(sigScheme tls.SignatureScheme) (hash.Algorithm, signature.Algorithm, error) {
var sigAlg signature.Algorithm
var hashAlg hash.Algorithm
if signature.Algorithm(sigScheme).IsPSS() {
// TLS 1.3 PSS scheme - full uint16 is the signature algorithm
sigAlg = signature.Algorithm(sigScheme)
hashAlg = hash.ExtractHashFromPSS(uint16(sigScheme))
if hashAlg == hash.None {
return 0, 0, fmt.Errorf("SignatureScheme %04x: %w", sigScheme, errInvalidHashAlgorithm)
}
} else {
// TLS 1.2 style - split into hash (high byte) and signature (low byte)
sigAlg = signature.Algorithm(sigScheme & 0xFF)
hashAlg = hash.Algorithm(sigScheme >> 8)
}
// Validate signature algorithm
if _, ok := signature.Algorithms()[sigAlg]; !ok {
return 0, 0, fmt.Errorf("SignatureScheme %04x: %w", sigScheme, errInvalidSignatureAlgorithm)
}
// Validate hash algorithm
if _, ok := hash.Algorithms()[hashAlg]; !ok || (ok && hashAlg == hash.None) {
return 0, 0, fmt.Errorf("SignatureScheme %04x: %w", sigScheme, errInvalidHashAlgorithm)
}
return hashAlg, sigAlg, nil
}

View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package signaturehash
import (
"github.com/pion/dtls/v3/pkg/crypto/hash"
"github.com/pion/dtls/v3/pkg/crypto/signature"
)
// Algorithms13 returns signature algorithms compatible with DTLS 1.3. This.
// includes DTLS 1.3-specific schemes like RSA-PSS in addition to DTLS 1.2 schemes.
//
// IMPORTANT: order in this slice determines priority used by SelectSignatureScheme13.
//
// Order follows industry standard preference (ECDSA-first) as used by OpenSSL,
// BoringSSL, Firefox, Chrome, and other major TLS 1.3 implementations.
func Algorithms13() []Algorithm {
return []Algorithm{
// ECDSA schemes (modern, efficient - industry standard preference)
{hash.SHA256, signature.ECDSA},
{hash.SHA384, signature.ECDSA},
{hash.SHA512, signature.ECDSA},
// Ed25519
{hash.Ed25519, signature.Ed25519},
// RSA-PSS RSAE schemes (TLS 1.3 / DTLS 1.3 compatible with standard RSA certs)
// Note: We only offer RSA_PSS_RSAE variants (0x0804-0x0806), not RSA_PSS_PSS
// (0x0809-0x080b). RSA-PSS certificates with OID id-RSASSA-PSS are virtually
// unused in the real world and are not allowed by the CA/Browser Forum Baseline
// Requirements for WebPKI. We avoid unnecessary complexity for certificates that
// don't exist in practice, following the pragmatic approach of Go's crypto/tls
// and BoringSSL: target real-world WebPKI use cases rather than RFC completeness.
// RSA_PSS_PSS schemes are parsed for wire-format compatibility but never negotiated.
{hash.SHA256, signature.RSA_PSS_RSAE_SHA256},
{hash.SHA384, signature.RSA_PSS_RSAE_SHA384},
{hash.SHA512, signature.RSA_PSS_RSAE_SHA512},
// {hash.SHA256, signature.RSA_PSS_PSS_SHA256},
// {hash.SHA384, signature.RSA_PSS_PSS_SHA384},
// {hash.SHA512, signature.RSA_PSS_PSS_SHA512},
// RSA PKCS#1 v1.5 schemes (backward compatibility with DTLS 1.2)
{hash.SHA256, signature.RSA},
{hash.SHA384, signature.RSA},
{hash.SHA512, signature.RSA},
}
}

111
vendor/github.com/pion/dtls/v3/pkg/net/net.go generated vendored Normal file
View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package net defines packet-oriented primitives that are compatible with net
// in the standard library.
package net
import (
"net"
"time"
)
// A PacketListener is the same as net.Listener but returns a net.PacketConn on
// Accept() rather than a net.Conn.
//
// Multiple goroutines may invoke methods on a PacketListener simultaneously.
type PacketListener interface {
// Accept waits for and returns the next connection to the listener.
Accept() (net.PacketConn, net.Addr, error)
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
Close() error
// Addr returns the listener's network address.
Addr() net.Addr
}
// PacketListenerFromListener converts a net.Listener into a
// dtlsnet.PacketListener.
func PacketListenerFromListener(l net.Listener) PacketListener {
return &packetListenerWrapper{
l: l,
}
}
// packetListenerWrapper wraps a net.Listener and implements
// dtlsnet.PacketListener.
type packetListenerWrapper struct {
l net.Listener
}
// Accept calls Accept on the underlying net.Listener and converts the returned
// net.Conn into a net.PacketConn.
func (p *packetListenerWrapper) Accept() (net.PacketConn, net.Addr, error) {
c, err := p.l.Accept()
if err != nil {
return PacketConnFromConn(c), nil, err
}
return PacketConnFromConn(c), c.RemoteAddr(), nil
}
// Close closes the underlying net.Listener.
func (p *packetListenerWrapper) Close() error {
return p.l.Close()
}
// Addr returns the address of the underlying net.Listener.
func (p *packetListenerWrapper) Addr() net.Addr {
return p.l.Addr()
}
// PacketConnFromConn converts a net.Conn into a net.PacketConn.
func PacketConnFromConn(conn net.Conn) net.PacketConn {
return &packetConnWrapper{conn}
}
// packetConnWrapper wraps a net.Conn and implements net.PacketConn.
type packetConnWrapper struct {
conn net.Conn
}
// ReadFrom reads from the underlying net.Conn and returns its remote address.
func (p *packetConnWrapper) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := p.conn.Read(b)
return n, p.conn.RemoteAddr(), err
}
// WriteTo writes to the underlying net.Conn.
func (p *packetConnWrapper) WriteTo(b []byte, _ net.Addr) (int, error) {
n, err := p.conn.Write(b)
return n, err
}
// Close closes the underlying net.Conn.
func (p *packetConnWrapper) Close() error {
return p.conn.Close()
}
// LocalAddr returns the local address of the underlying net.Conn.
func (p *packetConnWrapper) LocalAddr() net.Addr {
return p.conn.LocalAddr()
}
// SetDeadline sets the deadline on the underlying net.Conn.
func (p *packetConnWrapper) SetDeadline(t time.Time) error {
return p.conn.SetDeadline(t)
}
// SetReadDeadline sets the read deadline on the underlying net.Conn.
func (p *packetConnWrapper) SetReadDeadline(t time.Time) error {
return p.conn.SetReadDeadline(t)
}
// SetWriteDeadline sets the write deadline on the underlying net.Conn.
func (p *packetConnWrapper) SetWriteDeadline(t time.Time) error {
return p.conn.SetWriteDeadline(t)
}

View file

@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package alert implements TLS alert protocol https://tools.ietf.org/html/rfc5246#section-7.2
package alert
import (
"errors"
"fmt"
"github.com/pion/dtls/v3/pkg/protocol"
)
var errBufferTooSmall = &protocol.TemporaryError{Err: errors.New("buffer is too small")} //nolint:err113
// Level is the level of the TLS Alert.
type Level byte
// Level enums.
const (
Warning Level = 1
Fatal Level = 2
)
func (l Level) String() string {
switch l {
case Warning:
return "Warning"
case Fatal:
return "Fatal"
default:
return "Invalid alert level"
}
}
// Description is the extended info of the TLS Alert.
type Description byte
// Description enums.
const (
CloseNotify Description = 0
UnexpectedMessage Description = 10
BadRecordMac Description = 20
DecryptionFailed Description = 21
RecordOverflow Description = 22
DecompressionFailure Description = 30
HandshakeFailure Description = 40
NoCertificate Description = 41
BadCertificate Description = 42
UnsupportedCertificate Description = 43
CertificateRevoked Description = 44
CertificateExpired Description = 45
CertificateUnknown Description = 46
IllegalParameter Description = 47
UnknownCA Description = 48
AccessDenied Description = 49
DecodeError Description = 50
DecryptError Description = 51
ExportRestriction Description = 60
ProtocolVersion Description = 70
InsufficientSecurity Description = 71
InternalError Description = 80
UserCanceled Description = 90
NoRenegotiation Description = 100
UnsupportedExtension Description = 110
NoApplicationProtocol Description = 120
)
func (d Description) String() string { //nolint:cyclop
switch d {
case CloseNotify:
return "CloseNotify"
case UnexpectedMessage:
return "UnexpectedMessage"
case BadRecordMac:
return "BadRecordMac"
case DecryptionFailed:
return "DecryptionFailed"
case RecordOverflow:
return "RecordOverflow"
case DecompressionFailure:
return "DecompressionFailure"
case HandshakeFailure:
return "HandshakeFailure"
case NoCertificate:
return "NoCertificate"
case BadCertificate:
return "BadCertificate"
case UnsupportedCertificate:
return "UnsupportedCertificate"
case CertificateRevoked:
return "CertificateRevoked"
case CertificateExpired:
return "CertificateExpired"
case CertificateUnknown:
return "CertificateUnknown"
case IllegalParameter:
return "IllegalParameter"
case UnknownCA:
return "UnknownCA"
case AccessDenied:
return "AccessDenied"
case DecodeError:
return "DecodeError"
case DecryptError:
return "DecryptError"
case ExportRestriction:
return "ExportRestriction"
case ProtocolVersion:
return "ProtocolVersion"
case InsufficientSecurity:
return "InsufficientSecurity"
case InternalError:
return "InternalError"
case UserCanceled:
return "UserCanceled"
case NoRenegotiation:
return "NoRenegotiation"
case UnsupportedExtension:
return "UnsupportedExtension"
case NoApplicationProtocol:
return "NoApplicationProtocol"
default:
return "Invalid alert description"
}
}
// Alert is one of the content types supported by the TLS record layer.
// Alert messages convey the severity of the message
// (warning or fatal) and a description of the alert. Alert messages
// with a level of fatal result in the immediate termination of the
// connection. In this case, other connections corresponding to the
// session may continue, but the session identifier MUST be invalidated,
// preventing the failed session from being used to establish new
// connections. Like other messages, alert messages are encrypted and
// compressed, as specified by the current connection state.
// https://tools.ietf.org/html/rfc5246#section-7.2
type Alert struct {
Level Level
Description Description
}
// ContentType returns the ContentType of this Content.
func (a Alert) ContentType() protocol.ContentType {
return protocol.ContentTypeAlert
}
// Marshal returns the encoded alert.
func (a *Alert) Marshal() ([]byte, error) {
return []byte{byte(a.Level), byte(a.Description)}, nil
}
// Unmarshal populates the alert from binary data.
func (a *Alert) Unmarshal(data []byte) error {
if len(data) != 2 {
return errBufferTooSmall
}
a.Level = Level(data[0])
a.Description = Description(data[1])
return nil
}
func (a *Alert) String() string {
return fmt.Sprintf("Alert %s: %s", a.Level, a.Description)
}

View file

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package protocol
// ApplicationData messages are carried by the record layer and are
// fragmented, compressed, and encrypted based on the current connection
// state. The messages are treated as transparent data to the record
// layer.
// https://tools.ietf.org/html/rfc5246#section-10
type ApplicationData struct {
Data []byte
}
// ContentType returns the ContentType of this content.
func (a ApplicationData) ContentType() ContentType {
return ContentTypeApplicationData
}
// Marshal encodes the ApplicationData to binary.
func (a *ApplicationData) Marshal() ([]byte, error) {
return append([]byte{}, a.Data...), nil
}
// Unmarshal populates the ApplicationData from binary.
func (a *ApplicationData) Unmarshal(data []byte) error {
a.Data = append([]byte{}, data...)
return nil
}

View file

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package protocol
// ChangeCipherSpec protocol exists to signal transitions in
// ciphering strategies. The protocol consists of a single message,
// which is encrypted and compressed under the current (not the pending)
// connection state. The message consists of a single byte of value 1.
// https://tools.ietf.org/html/rfc5246#section-7.1
type ChangeCipherSpec struct{}
// ContentType returns the ContentType of this content.
func (c ChangeCipherSpec) ContentType() ContentType {
return ContentTypeChangeCipherSpec
}
// Marshal encodes the ChangeCipherSpec to binary.
func (c *ChangeCipherSpec) Marshal() ([]byte, error) {
return []byte{0x01}, nil
}
// Unmarshal populates the ChangeCipherSpec from binary.
func (c *ChangeCipherSpec) Unmarshal(data []byte) error {
if len(data) == 1 && data[0] == 0x01 {
return nil
}
return errInvalidCipherSpec
}

View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package protocol
// CompressionMethodID is the ID for a CompressionMethod.
type CompressionMethodID byte
const (
compressionMethodNull CompressionMethodID = 0
)
// CompressionMethod represents a TLS Compression Method.
type CompressionMethod struct {
ID CompressionMethodID
}
// CompressionMethods returns all supported CompressionMethods.
func CompressionMethods() map[CompressionMethodID]*CompressionMethod {
return map[CompressionMethodID]*CompressionMethod{
compressionMethodNull: {ID: compressionMethodNull},
}
}
// DecodeCompressionMethods the given compression methods.
func DecodeCompressionMethods(buf []byte) ([]*CompressionMethod, error) {
if len(buf) < 1 {
return nil, errBufferTooSmall
}
compressionMethodsCount := int(buf[0])
c := []*CompressionMethod{}
for i := 0; i < compressionMethodsCount; i++ {
if len(buf) <= i+1 {
return nil, errBufferTooSmall
}
id := CompressionMethodID(buf[i+1])
if compressionMethod, ok := CompressionMethods()[id]; ok {
c = append(c, compressionMethod)
}
}
return c, nil
}
// EncodeCompressionMethods the given compression methods.
func EncodeCompressionMethods(c []*CompressionMethod) []byte {
out := []byte{byte(len(c))}
for i := len(c); i > 0; i-- {
out = append(out, byte(c[i-1].ID))
}
return out
}

25
vendor/github.com/pion/dtls/v3/pkg/protocol/content.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package protocol
// ContentType represents the IANA Registered ContentTypes
//
// https://tools.ietf.org/html/rfc4346#section-6.2.1
type ContentType uint8
// ContentType enums.
const (
ContentTypeChangeCipherSpec ContentType = 20
ContentTypeAlert ContentType = 21
ContentTypeHandshake ContentType = 22
ContentTypeApplicationData ContentType = 23
ContentTypeConnectionID ContentType = 25
)
// Content is the top level distinguisher for a DTLS Datagram.
type Content interface {
ContentType() ContentType
Marshal() ([]byte, error)
Unmarshal(data []byte) error
}

112
vendor/github.com/pion/dtls/v3/pkg/protocol/errors.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package protocol
import (
"errors"
"fmt"
"net"
)
var (
errBufferTooSmall = &TemporaryError{Err: errors.New("buffer is too small")} //nolint:err113
errInvalidCipherSpec = &FatalError{Err: errors.New("cipher spec invalid")} //nolint:err113
)
// FatalError indicates that the DTLS connection is no longer available.
// It is mainly caused by wrong configuration of server or client.
type FatalError struct {
Err error
}
// InternalError indicates and internal error caused by the implementation,
// and the DTLS connection is no longer available.
// It is mainly caused by bugs or tried to use unimplemented features.
type InternalError struct {
Err error
}
// TemporaryError indicates that the DTLS connection is still available, but the request was failed temporary.
type TemporaryError struct {
Err error
}
// TimeoutError indicates that the request was timed out.
type TimeoutError struct {
Err error
}
// HandshakeError indicates that the handshake failed.
type HandshakeError struct {
Err error
}
// Timeout implements net.Error.Timeout().
func (*FatalError) Timeout() bool { return false }
// Temporary implements net.Error.Temporary().
func (*FatalError) Temporary() bool { return false }
// Unwrap implements Go1.13 error unwrapper.
func (e *FatalError) Unwrap() error { return e.Err }
func (e *FatalError) Error() string { return fmt.Sprintf("dtls fatal: %v", e.Err) }
// Timeout implements net.Error.Timeout().
func (*InternalError) Timeout() bool { return false }
// Temporary implements net.Error.Temporary().
func (*InternalError) Temporary() bool { return false }
// Unwrap implements Go1.13 error unwrapper.
func (e *InternalError) Unwrap() error { return e.Err }
func (e *InternalError) Error() string { return fmt.Sprintf("dtls internal: %v", e.Err) }
// Timeout implements net.Error.Timeout().
func (*TemporaryError) Timeout() bool { return false }
// Temporary implements net.Error.Temporary().
func (*TemporaryError) Temporary() bool { return true }
// Unwrap implements Go1.13 error unwrapper.
func (e *TemporaryError) Unwrap() error { return e.Err }
func (e *TemporaryError) Error() string { return fmt.Sprintf("dtls temporary: %v", e.Err) }
// Timeout implements net.Error.Timeout().
func (*TimeoutError) Timeout() bool { return true }
// Temporary implements net.Error.Temporary().
func (*TimeoutError) Temporary() bool { return true }
// Unwrap implements Go1.13 error unwrapper.
func (e *TimeoutError) Unwrap() error { return e.Err }
func (e *TimeoutError) Error() string { return fmt.Sprintf("dtls timeout: %v", e.Err) }
// Timeout implements net.Error.Timeout().
func (e *HandshakeError) Timeout() bool {
var netErr net.Error
if errors.As(e.Err, &netErr) {
return netErr.Timeout()
}
return false
}
// Temporary implements net.Error.Temporary().
func (e *HandshakeError) Temporary() bool {
var netErr net.Error
if errors.As(e.Err, &netErr) {
return netErr.Temporary() //nolint
}
return false
}
// Unwrap implements Go1.13 error unwrapper.
func (e *HandshakeError) Unwrap() error { return e.Err }
func (e *HandshakeError) Error() string { return fmt.Sprintf("handshake error: %v", e.Err) }

Some files were not shown because too many files have changed in this diff Show more