diff --git a/README.md b/README.md index c288c71..4d6d05b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ sudo ./anylink -conf="conf/server.toml" - [x] IP分配(实现IP、MAC映射信息的持久化) - [x] TLS-TCP通道 +- [x] DTLS-UDP通道 - [x] 兼容AnyConnect - [x] 基于tun设备的nat访问模式 - [x] 基于tap设备的桥接访问模式 @@ -72,8 +73,6 @@ sudo ./anylink -conf="conf/server.toml" - [x] 后台管理界面 - [x] 访问权限管理 -- [ ] DTLS-UDP通道 - ## Config 默认配置文件内有详细的注释,根据注释填写配置即可。 diff --git a/dtls-2.0.9/.editorconfig b/dtls-2.0.9/.editorconfig new file mode 100644 index 0000000..d2b3206 --- /dev/null +++ b/dtls-2.0.9/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org/ + +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 diff --git a/dtls-2.0.9/.github/assert-contributors.sh b/dtls-2.0.9/.github/assert-contributors.sh new file mode 100644 index 0000000..12e6afe --- /dev/null +++ b/dtls-2.0.9/.github/assert-contributors.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +set -e + +SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +if [ -f ${SCRIPT_PATH}/.ci.conf ] +then + . ${SCRIPT_PATH}/.ci.conf +fi + +# +# DO NOT EDIT THIS +# +EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot') +# If you want to exclude a name from all repositories, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# If you want to exclude a name only from this repository, +# add EXCLUDED_CONTRIBUTORS=('name') to .github/.ci.conf + +MISSING_CONTRIBUTORS=() + +shouldBeIncluded () { + for i in "${EXCLUDED_CONTRIBUTORS[@]}" + do + if [ "$i" == "$1" ] ; then + return 1 + fi + done + return 0 +} + + +IFS=$'\n' #Only split on newline +for contributor in $(git log --format='%aN' | sort -u) +do + if shouldBeIncluded $contributor; then + if ! grep -q "$contributor" "$SCRIPT_PATH/../README.md"; then + MISSING_CONTRIBUTORS+=("$contributor") + fi + fi +done +unset IFS + +if [ ${#MISSING_CONTRIBUTORS[@]} -ne 0 ]; then + echo "Please add the following contributors to the README" + for i in "${MISSING_CONTRIBUTORS[@]}" + do + echo "$i" + done + exit 1 +fi diff --git a/dtls-2.0.9/.github/hooks/commit-msg.sh b/dtls-2.0.9/.github/hooks/commit-msg.sh new file mode 100644 index 0000000..8213dc2 --- /dev/null +++ b/dtls-2.0.9/.github/hooks/commit-msg.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# +# DO NOT EDIT THIS FILE DIRECTLY +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +set -e + +.github/lint-commit-message.sh $1 diff --git a/dtls-2.0.9/.github/hooks/pre-commit.sh b/dtls-2.0.9/.github/hooks/pre-commit.sh new file mode 100644 index 0000000..cc318d7 --- /dev/null +++ b/dtls-2.0.9/.github/hooks/pre-commit.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# +# DO NOT EDIT THIS FILE DIRECTLY +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +# Redirect output to stderr. +exec 1>&2 + +.github/lint-disallowed-functions-in-library.sh diff --git a/dtls-2.0.9/.github/hooks/pre-push.sh b/dtls-2.0.9/.github/hooks/pre-push.sh new file mode 100644 index 0000000..7cb2365 --- /dev/null +++ b/dtls-2.0.9/.github/hooks/pre-push.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# +# DO NOT EDIT THIS FILE DIRECTLY +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +set -e + +.github/assert-contributors.sh + +exit 0 diff --git a/dtls-2.0.9/.github/install-hooks.sh b/dtls-2.0.9/.github/install-hooks.sh new file mode 100644 index 0000000..73d20a4 --- /dev/null +++ b/dtls-2.0.9/.github/install-hooks.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +cp "$SCRIPT_PATH/hooks/commit-msg.sh" "$SCRIPT_PATH/../.git/hooks/commit-msg" +cp "$SCRIPT_PATH/hooks/pre-commit.sh" "$SCRIPT_PATH/../.git/hooks/pre-commit" +cp "$SCRIPT_PATH/hooks/pre-push.sh" "$SCRIPT_PATH/../.git/hooks/pre-push" diff --git a/dtls-2.0.9/.github/lint-commit-message.sh b/dtls-2.0.9/.github/lint-commit-message.sh new file mode 100644 index 0000000..010a332 --- /dev/null +++ b/dtls-2.0.9/.github/lint-commit-message.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +set -e + +display_commit_message_error() { +cat << EndOfMessage +$1 + +------------------------------------------------- +The preceding commit message is invalid +it failed '$2' of the following checks + +* Separate subject from body with a blank line +* Limit the subject line to 50 characters +* Capitalize the subject line +* Do not end the subject line with a period +* Wrap the body at 72 characters +EndOfMessage + + exit 1 +} + +lint_commit_message() { + if [[ "$(echo "$1" | awk 'NR == 2 {print $1;}' | wc -c)" -ne 1 ]]; then + display_commit_message_error "$1" 'Separate subject from body with a blank line' + fi + + if [[ "$(echo "$1" | head -n1 | awk '{print length}')" -gt 50 ]]; then + display_commit_message_error "$1" 'Limit the subject line to 50 characters' + fi + + if [[ ! $1 =~ ^[A-Z] ]]; then + display_commit_message_error "$1" 'Capitalize the subject line' + fi + + if [[ "$(echo "$1" | awk 'NR == 1 {print substr($0,length($0),1)}')" == "." ]]; then + display_commit_message_error "$1" 'Do not end the subject line with a period' + fi + + if [[ "$(echo "$1" | awk '{print length}' | sort -nr | head -1)" -gt 72 ]]; then + display_commit_message_error "$1" 'Wrap the body at 72 characters' + fi +} + +if [ "$#" -eq 1 ]; then + if [ ! -f "$1" ]; then + echo "$0 was passed one argument, but was not a valid file" + exit 1 + fi + lint_commit_message "$(sed -n '/# Please enter the commit message for your changes. Lines starting/q;p' "$1")" +else + for commit in $(git rev-list --no-merges origin/master..); do + lint_commit_message "$(git log --format="%B" -n 1 $commit)" + done +fi diff --git a/dtls-2.0.9/.github/lint-disallowed-functions-in-library.sh b/dtls-2.0.9/.github/lint-disallowed-functions-in-library.sh new file mode 100644 index 0000000..21e48da --- /dev/null +++ b/dtls-2.0.9/.github/lint-disallowed-functions-in-library.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +set -e + +# Disallow usages of functions that cause the program to exit in the library code +SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +if [ -f ${SCRIPT_PATH}/.ci.conf ] +then + . ${SCRIPT_PATH}/.ci.conf +fi + +EXCLUDE_DIRECTORIES=${DISALLOWED_FUNCTIONS_EXCLUDED_DIRECTORIES:-"examples"} +DISALLOWED_FUNCTIONS=('os.Exit(' 'panic(' 'Fatal(' 'Fatalf(' 'Fatalln(' 'fmt.Println(' 'fmt.Printf(' 'log.Print(' 'log.Println(' 'log.Printf(') + +files=$( + find "$SCRIPT_PATH/.." -name "*.go" \ + | grep -v -e '^.*_test.go$' \ + | while read file + do + excluded=false + for ex in $EXCLUDE_DIRECTORIES + do + if [[ $file == */$ex/* ]] + then + excluded=true + break + fi + done + $excluded || echo "$file" + done +) + +for disallowedFunction in "${DISALLOWED_FUNCTIONS[@]}" +do + if grep -e "$disallowedFunction" $files | grep -v -e 'nolint'; then + echo "$disallowedFunction may only be used in example code" + exit 1 + fi +done diff --git a/dtls-2.0.9/.github/lint-filename.sh b/dtls-2.0.9/.github/lint-filename.sh new file mode 100644 index 0000000..81b3f14 --- /dev/null +++ b/dtls-2.0.9/.github/lint-filename.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +set -e + +SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +GO_REGEX="^[a-zA-Z][a-zA-Z0-9_]*\.go$" + +find "$SCRIPT_PATH/.." -name "*.go" | while read fullpath; do + filename=$(basename -- "$fullpath") + + if ! [[ $filename =~ $GO_REGEX ]]; then + echo "$filename is not a valid filename for Go code, only alpha, numbers and underscores are supported" + exit 1 + fi +done diff --git a/dtls-2.0.9/.github/workflows/e2e.yaml b/dtls-2.0.9/.github/workflows/e2e.yaml new file mode 100644 index 0000000..c6b4cf4 --- /dev/null +++ b/dtls-2.0.9/.github/workflows/e2e.yaml @@ -0,0 +1,20 @@ +name: E2E +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + e2e-test: + name: Test + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: test + run: | + docker build -t pion-dtls-e2e -f e2e/Dockerfile . + docker run -i --rm pion-dtls-e2e diff --git a/dtls-2.0.9/.github/workflows/lint.yaml b/dtls-2.0.9/.github/workflows/lint.yaml new file mode 100644 index 0000000..8824c34 --- /dev/null +++ b/dtls-2.0.9/.github/workflows/lint.yaml @@ -0,0 +1,43 @@ +name: Lint +on: + pull_request: + types: + - opened + - edited + - synchronize +jobs: + lint-commit-message: + name: Metadata + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Commit Message + run: .github/lint-commit-message.sh + + - name: File names + run: .github/lint-filename.sh + + - name: Contributors + run: .github/assert-contributors.sh + + - name: Functions + run: .github/lint-disallowed-functions-in-library.sh + + lint-go: + name: Go + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v2 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.31 + args: $GOLANGCI_LINT_EXRA_ARGS diff --git a/dtls-2.0.9/.github/workflows/renovate-go-mod-fix.yaml b/dtls-2.0.9/.github/workflows/renovate-go-mod-fix.yaml new file mode 100644 index 0000000..46d2d04 --- /dev/null +++ b/dtls-2.0.9/.github/workflows/renovate-go-mod-fix.yaml @@ -0,0 +1,33 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +name: go-mod-fix +on: + push: + branches: + - renovate/* + +jobs: + go-mod-fix: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: fix + uses: at-wat/go-sum-fix-action@v0 + with: + git_user: Pion Bot + git_email: 59523206+pionbot@users.noreply.github.com + github_token: ${{ secrets.PIONBOT_PRIVATE_KEY }} + commit_style: squash + push: force diff --git a/dtls-2.0.9/.github/workflows/test.yaml b/dtls-2.0.9/.github/workflows/test.yaml new file mode 100644 index 0000000..5b7a43b --- /dev/null +++ b/dtls-2.0.9/.github/workflows/test.yaml @@ -0,0 +1,139 @@ +name: Test +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + go: ["1.15", "1.16"] + fail-fast: false + name: Go ${{ matrix.go }} + steps: + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/go/bin + ~/.cache + key: ${{ runner.os }}-amd64-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-amd64-go- + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Setup go-acc + run: | + go get github.com/ory/go-acc + git checkout go.mod go.sum + + - name: Run test + run: | + go-acc -o cover.out ./... -- \ + -bench=. \ + -v -race + + - uses: codecov/codecov-action@v1 + with: + file: ./cover.out + name: codecov-umbrella + fail_ci_if_error: true + flags: go + + test-i386: + runs-on: ubuntu-latest + strategy: + matrix: + go: ["1.15", "1.16"] + fail-fast: false + name: Go i386 ${{ matrix.go }} + steps: + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache + key: ${{ runner.os }}-i386-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-i386-go- + + - name: Run test + run: | + mkdir -p $HOME/go/pkg/mod $HOME/.cache + docker run \ + -u $(id -u):$(id -g) \ + -e "GO111MODULE=on" \ + -e "CGO_ENABLED=0" \ + -v $GITHUB_WORKSPACE:/go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ + -v $HOME/go/pkg/mod:/go/pkg/mod \ + -v $HOME/.cache:/.cache \ + -w /go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ + i386/golang:${{matrix.go}}-alpine \ + /usr/local/go/bin/go test \ + ${TEST_EXTRA_ARGS:-} \ + -v ./... + + test-wasm: + runs-on: ubuntu-latest + strategy: + fail-fast: false + name: WASM + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '12.x' + + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache + key: ${{ runner.os }}-wasm-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-wasm-go- + + - name: Download Go + run: curl -sSfL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar -C ~ -xzf - + env: + GO_VERSION: 1.16 + + - name: Set Go Root + run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV + + - name: Set Go Path + run: echo "GOPATH=${HOME}/go" >> $GITHUB_ENV + + - name: Set Go Path + run: echo "GO_JS_WASM_EXEC=${GOROOT}/misc/wasm/go_js_wasm_exec" >> $GITHUB_ENV + + - name: Insall NPM modules + run: yarn install + + - name: Run Tests + run: | + GOOS=js GOARCH=wasm $GOPATH/bin/go test \ + -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ./... + + - uses: codecov/codecov-action@v1 + with: + file: ./cover.out + name: codecov-umbrella + fail_ci_if_error: true + flags: wasm diff --git a/dtls-2.0.9/.github/workflows/tidy-check.yaml b/dtls-2.0.9/.github/workflows/tidy-check.yaml new file mode 100644 index 0000000..03b5189 --- /dev/null +++ b/dtls-2.0.9/.github/workflows/tidy-check.yaml @@ -0,0 +1,37 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +name: Go mod tidy +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + Check: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2 + - name: check + run: | + go mod download + go mod tidy + if ! git diff --exit-code + then + echo "Not go mod tidied" + exit 1 + fi diff --git a/dtls-2.0.9/.gitignore b/dtls-2.0.9/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/dtls-2.0.9/.gitignore @@ -0,0 +1,24 @@ +### 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 diff --git a/dtls-2.0.9/.golangci.yml b/dtls-2.0.9/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/dtls-2.0.9/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions 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 + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - 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. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - 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. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - 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 + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/dtls-2.0.9/LICENSE b/dtls-2.0.9/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/dtls-2.0.9/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +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. diff --git a/dtls-2.0.9/Makefile b/dtls-2.0.9/Makefile new file mode 100644 index 0000000..1df38b2 --- /dev/null +++ b/dtls-2.0.9/Makefile @@ -0,0 +1,6 @@ +fuzz-build-record-layer: fuzz-prepare + go-fuzz-build -tags gofuzz -func FuzzRecordLayer +fuzz-run-record-layer: + go-fuzz -bin dtls-fuzz.zip -workdir fuzz +fuzz-prepare: + @GO111MODULE=on go mod vendor diff --git a/dtls-2.0.9/README.md b/dtls-2.0.9/README.md new file mode 100644 index 0000000..9f7e4a0 --- /dev/null +++ b/dtls-2.0.9/README.md @@ -0,0 +1,156 @@ +

+
+ Pion DTLS +
+

+

A Go implementation of DTLS

+

+ Pion DTLS + Sourcegraph Widget + Slack Widget +
+ Build Status + GoDoc + Coverage Status + Go Report Card + Codacy Badge + License: MIT +

+
+ +Native [DTLS 1.2][rfc6347] implementation in the Go programming language. + +A long term goal is a professional security review, and maye inclusion in stdlib. + +[rfc6347]: https://tools.ietf.org/html/rfc6347 + +### Goals/Progress +This will only be targeting DTLS 1.2, and the most modern/common cipher suites. +We would love contributes that fall under the 'Planned Features' and fixing any bugs! + +#### 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]) + +[rfc5705]: https://tools.ietf.org/html/rfc5705 +[rfc7627]: https://tools.ietf.org/html/rfc7627 + +#### 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_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_128_GCM_SHA256 ([RFC 5487][rfc5487]) +* TLS_PSK_WITH_AES_128_CBC_SHA256 ([RFC 5487][rfc5487]) + +[rfc5289]: https://tools.ietf.org/html/rfc5289 +[rfc8422]: https://tools.ietf.org/html/rfc8422 +[rfc6655]: https://tools.ietf.org/html/rfc6655 +[rfc5487]: https://tools.ietf.org/html/rfc5487 + +#### Planned Features +* Chacha20Poly1305 + +#### Excluded Features +* DTLS 1.0 +* Renegotiation +* Compression + +### Using + +This library needs at least Go 1.13, 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 +``` + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Public API* +* [Chris Hiszpanski](https://github.com/thinkski) - *Support Signature Algorithms Extension* +* [Iñigo Garcia Olaizola](https://github.com/igolaizola) - *Serialization & resumption, cert verification, E2E* +* [Daniele Sluijters](https://github.com/daenney) - *AES-CCM support* +* [Jin Lei](https://github.com/jinleileiking) - *Logging* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Lander Noterman](https://github.com/LanderN) +* [Aleksandr Razumov](https://github.com/ernado) - *Fuzzing* +* [Ryan Gordon](https://github.com/ryangordon) +* [Stefan Tatschner](https://rumpelsepp.org/contact.html) +* [Hayden James](https://github.com/hjames9) +* [Jozef Kralik](https://github.com/jkralik) +* [Robert Eperjesi](https://github.com/epes) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Julien Salleyron](https://github.com/juliens) - *Server Name Indication* +* [Jeroen de Bruijn](https://github.com/vidavidorra) +* [bjdgyc](https://github.com/bjdgyc) +* [Jeffrey Stoke (Jeff Ctor)](https://github.com/jeffreystoke) - *Fragmentbuffer Fix* +* [Frank Olbricht](https://github.com/folbricht) +* [ZHENK](https://github.com/scorpionknifes) +* [Carson Hoffman](https://github.com/CarsonHoffman) +* [Vadim Filimonov](https://github.com/fffilimonov) +* [Jim Wert](https://github.com/bocajim) +* [Alvaro Viebrantz](https://github.com/alvarowolfx) +* [Kegan Dougal](https://github.com/Kegsay) +* [Michael Zabka](https://github.com/misak113) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/dtls-2.0.9/bench_test.go b/dtls-2.0.9/bench_test.go new file mode 100644 index 0000000..517bc16 --- /dev/null +++ b/dtls-2.0.9/bench_test.go @@ -0,0 +1,118 @@ +package dtls + +import ( + "context" + "crypto/tls" + "fmt" + "testing" + "time" + + "github.com/pion/dtls/v2/internal/net/dpipe" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/logging" + "github.com/pion/transport/test" +) + +func TestSimpleReadWrite(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + certificate, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + gotHello := make(chan struct{}) + + go func() { + server, sErr := testServer(ctx, cb, &Config{ + Certificates: []tls.Certificate{certificate}, + LoggerFactory: logging.NewDefaultLoggerFactory(), + }, false) + if sErr != nil { + t.Error(sErr) + return + } + buf := make([]byte, 1024) + if _, sErr = server.Read(buf); sErr != nil { + t.Error(sErr) + } + gotHello <- struct{}{} + if sErr = server.Close(); sErr != nil { + t.Error(sErr) + } + }() + + client, err := testClient(ctx, ca, &Config{ + LoggerFactory: logging.NewDefaultLoggerFactory(), + InsecureSkipVerify: true, + }, false) + if err != nil { + t.Fatal(err) + } + if _, err = client.Write([]byte("hello")); err != nil { + t.Error(err) + } + select { + case <-gotHello: + // OK + case <-time.After(time.Second * 5): + t.Error("timeout") + } + + if err = client.Close(); err != nil { + t.Error(err) + } +} + +func benchmarkConn(b *testing.B, n int64) { + b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { + ctx := context.Background() + + ca, cb := dpipe.Pipe() + certificate, err := selfsign.GenerateSelfSigned() + server := make(chan *Conn) + go func() { + s, sErr := testServer(ctx, cb, &Config{ + Certificates: []tls.Certificate{certificate}, + }, false) + if err != nil { + b.Error(sErr) + return + } + server <- s + }() + if err != nil { + b.Fatal(err) + } + hw := make([]byte, n) + b.ReportAllocs() + b.SetBytes(int64(len(hw))) + go func() { + client, cErr := testClient(ctx, ca, &Config{InsecureSkipVerify: true}, false) + if cErr != nil { + b.Error(err) + } + for { + if _, cErr = client.Write(hw); cErr != nil { + b.Error(err) + } + } + }() + s := <-server + buf := make([]byte, 2048) + for i := 0; i < b.N; i++ { + if _, err = s.Read(buf); err != nil { + b.Error(err) + } + } + }) +} + +func BenchmarkConnReadWrite(b *testing.B) { + for _, n := range []int64{16, 128, 512, 1024, 2048} { + benchmarkConn(b, n) + } +} diff --git a/dtls-2.0.9/certificate.go b/dtls-2.0.9/certificate.go new file mode 100644 index 0000000..c99e1c9 --- /dev/null +++ b/dtls-2.0.9/certificate.go @@ -0,0 +1,67 @@ +package dtls + +import ( + "crypto/tls" + "crypto/x509" + "strings" +) + +func (c *handshakeConfig) getCertificate(serverName string) (*tls.Certificate, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.nameToCertificate == nil { + 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 + } + + 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(serverName) == 0 { + return &c.localCertificates[0], nil + } + + name := strings.TrimRight(strings.ToLower(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 +} diff --git a/dtls-2.0.9/certificate_test.go b/dtls-2.0.9/certificate_test.go new file mode 100644 index 0000000..56cc04e --- /dev/null +++ b/dtls-2.0.9/certificate_test.go @@ -0,0 +1,79 @@ +package dtls + +import ( + "crypto/tls" + "reflect" + "testing" + + "github.com/pion/dtls/v2/pkg/crypto/selfsign" +) + +func TestGetCertificate(t *testing.T) { + certificateWildcard, err := selfsign.GenerateSelfSignedWithDNS("*.test.test") + if err != nil { + t.Fatal(err) + } + + certificateTest, err := selfsign.GenerateSelfSignedWithDNS("test.test", "www.test.test", "pop.test.test") + if err != nil { + t.Fatal(err) + } + + certificateRandom, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + + cfg := &handshakeConfig{ + localCertificates: []tls.Certificate{ + certificateRandom, + certificateTest, + certificateWildcard, + }, + } + + testCases := []struct { + desc string + serverName string + expectedCertificate tls.Certificate + }{ + { + desc: "Simple match in CN", + serverName: "test.test", + expectedCertificate: certificateTest, + }, + { + desc: "Simple match in SANs", + serverName: "www.test.test", + expectedCertificate: certificateTest, + }, + + { + desc: "Wildcard match", + serverName: "foo.test.test", + expectedCertificate: certificateWildcard, + }, + { + desc: "No match return first", + serverName: "foo.bar", + expectedCertificate: certificateRandom, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + cert, err := cfg.getCertificate(test.serverName) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(cert.Leaf, test.expectedCertificate.Leaf) { + t.Fatalf("Certificate does not match: expected(%v) actual(%v)", test.expectedCertificate.Leaf, cert.Leaf) + } + }) + } +} diff --git a/dtls-2.0.9/cipher_suite.go b/dtls-2.0.9/cipher_suite.go new file mode 100644 index 0000000..ed10609 --- /dev/null +++ b/dtls-2.0.9/cipher_suite.go @@ -0,0 +1,213 @@ +package dtls + +import ( + "fmt" + "hash" + + "github.com/pion/dtls/v2/internal/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// CipherSuiteID is an ID for our supported CipherSuites +type CipherSuiteID = ciphersuite.ID + +// Supported Cipher Suites +const ( + // AES-128-CCM + TLS_ECDHE_ECDSA_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM //nolint:golint,stylecheck + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 //nolint:golint,stylecheck + + // AES-128-GCM-SHA256 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + + // AES-256-CBC-SHA + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA //nolint:golint,stylecheck + + TLS_PSK_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM_8 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CBC_SHA256 //nolint:golint,stylecheck +) + +// 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 +) + +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 + + // 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(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 { + 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_128_GCM_SHA256: + return &ciphersuite.TLSPskWithAes128GcmSha256{} + case TLS_PSK_WITH_AES_128_CBC_SHA256: + return &ciphersuite.TLSPskWithAes128CbcSha256{} + } + + 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{}, + } +} + +func allCipherSuites() []CipherSuite { + return []CipherSuite{ + ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm(), + ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8(), + &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{}, + &ciphersuite.TLSEcdheRsaWithAes256CbcSha{}, + ciphersuite.NewTLSPskWithAes128Ccm(), + ciphersuite.NewTLSPskWithAes128Ccm8(), + &ciphersuite.TLSPskWithAes128GcmSha256{}, + } +} + +func cipherSuiteIDs(cipherSuites []CipherSuite) []uint16 { + rtrn := []uint16{} + for _, c := range cipherSuites { + rtrn = append(rtrn, uint16(c.ID())) + } + return rtrn +} + +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, &invalidCipherSuite{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 +} diff --git a/dtls-2.0.9/cipher_suite_go114.go b/dtls-2.0.9/cipher_suite_go114.go new file mode 100644 index 0000000..7bba16e --- /dev/null +++ b/dtls-2.0.9/cipher_suite_go114.go @@ -0,0 +1,40 @@ +// +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 +} diff --git a/dtls-2.0.9/cipher_suite_go114_test.go b/dtls-2.0.9/cipher_suite_go114_test.go new file mode 100644 index 0000000..57c64d4 --- /dev/null +++ b/dtls-2.0.9/cipher_suite_go114_test.go @@ -0,0 +1,51 @@ +// +build go1.14 + +package dtls + +import ( + "testing" +) + +func TestInsecureCipherSuites(t *testing.T) { + r := InsecureCipherSuites() + + if len(r) != 0 { + t.Fatalf("Expected no insecure ciphersuites, got %d", len(r)) + } +} + +func TestCipherSuites(t *testing.T) { + ours := allCipherSuites() + theirs := CipherSuites() + + if len(ours) != len(theirs) { + t.Fatalf("Expected %d CipherSuites, got %d", len(ours), len(theirs)) + } + + for i, s := range ours { + i := i + s := s + t.Run(s.String(), func(t *testing.T) { + c := theirs[i] + if c.ID != uint16(s.ID()) { + t.Fatalf("Expected ID: 0x%04X, got 0x%04X", s.ID(), c.ID) + } + + if c.Name != s.String() { + t.Fatalf("Expected Name: %s, got %s", s.String(), c.Name) + } + + if len(c.SupportedVersions) != 1 { + t.Fatalf("Expected %d SupportedVersion, got %d", 1, len(c.SupportedVersions)) + } + + if c.SupportedVersions[0] != VersionDTLS12 { + t.Fatalf("Expected SupportedVersions 0x%04X, got 0x%04X", VersionDTLS12, c.SupportedVersions[0]) + } + + if c.Insecure { + t.Fatalf("Expected Insecure %t, got %t", false, c.Insecure) + } + }) + } +} diff --git a/dtls-2.0.9/cipher_suite_test.go b/dtls-2.0.9/cipher_suite_test.go new file mode 100644 index 0000000..e0ed6d1 --- /dev/null +++ b/dtls-2.0.9/cipher_suite_test.go @@ -0,0 +1,108 @@ +package dtls + +import ( + "context" + "testing" + "time" + + "github.com/pion/dtls/v2/internal/ciphersuite" + "github.com/pion/dtls/v2/internal/net/dpipe" + "github.com/pion/transport/test" +) + +func TestCipherSuiteName(t *testing.T) { + testCases := []struct { + suite CipherSuiteID + expected string + }{ + {TLS_ECDHE_ECDSA_WITH_AES_128_CCM, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM"}, + {CipherSuiteID(0x0000), "0x0000"}, + } + + for _, testCase := range testCases { + res := CipherSuiteName(testCase.suite) + if res != testCase.expected { + t.Fatalf("Expected: %s, got %s", testCase.expected, res) + } + } +} + +func TestAllCipherSuites(t *testing.T) { + actual := len(allCipherSuites()) + if actual == 0 { + t.Fatal() + } +} + +// CustomCipher that is just used to assert Custom IDs work +type testCustomCipherSuite struct { + ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256 + authenticationType CipherSuiteAuthenticationType +} + +func (t *testCustomCipherSuite) ID() CipherSuiteID { + return 0xFFFF +} + +func (t *testCustomCipherSuite) AuthenticationType() CipherSuiteAuthenticationType { + return t.authenticationType +} + +// Assert that two connections that pass in a CipherSuite with a CustomID works +func TestCustomCipherSuite(t *testing.T) { + type result struct { + c *Conn + err error + } + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + runTest := func(cipherFactory func() []CipherSuite) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + c := make(chan result) + + go func() { + client, err := testClient(ctx, ca, &Config{ + CipherSuites: []CipherSuiteID{}, + CustomCipherSuites: cipherFactory, + }, true) + c <- result{client, err} + }() + + server, err := testServer(ctx, cb, &Config{ + CipherSuites: []CipherSuiteID{}, + CustomCipherSuites: cipherFactory, + }, true) + + clientResult := <-c + + if err != nil { + t.Error(err) + } else { + _ = server.Close() + } + + if clientResult.err != nil { + t.Error(clientResult.err) + } else { + _ = clientResult.c.Close() + } + } + + t.Run("Custom ID", func(t *testing.T) { + runTest(func() []CipherSuite { + return []CipherSuite{&testCustomCipherSuite{authenticationType: CipherSuiteAuthenticationTypeCertificate}} + }) + }) + + t.Run("Anonymous Cipher", func(t *testing.T) { + runTest(func() []CipherSuite { + return []CipherSuite{&testCustomCipherSuite{authenticationType: CipherSuiteAuthenticationTypeAnonymous}} + }) + }) +} diff --git a/dtls-2.0.9/codecov.yml b/dtls-2.0.9/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/dtls-2.0.9/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +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/**/*" diff --git a/dtls-2.0.9/compression_method.go b/dtls-2.0.9/compression_method.go new file mode 100644 index 0000000..693eb7a --- /dev/null +++ b/dtls-2.0.9/compression_method.go @@ -0,0 +1,9 @@ +package dtls + +import "github.com/pion/dtls/v2/pkg/protocol" + +func defaultCompressionMethods() []*protocol.CompressionMethod { + return []*protocol.CompressionMethod{ + {}, + } +} diff --git a/dtls-2.0.9/config.go b/dtls-2.0.9/config.go new file mode 100644 index 0000000..7c1c0b7 --- /dev/null +++ b/dtls-2.0.9/config.go @@ -0,0 +1,197 @@ +package dtls + +import ( + "context" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "io" + "time" + + "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. +type Config struct { + // 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 + + // 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 + + // 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 + + // 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 + + CiscoCompat PSKCallback // TODO add cisco anyconnect support + + // 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 + + // 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 + + // ConnectContextMaker is a function to make a context used in Dial(), + // Client(), Server(), and Accept(). If nil, the default ConnectContextMaker + // is used. It can be implemented as following. + // + // func ConnectContextMaker() (context.Context, func()) { + // return context.WithTimeout(context.Background(), 30*time.Second) + // } + ConnectContextMaker func() (context.Context, func()) + + // 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 +} + +func defaultConnectContextMaker() (context.Context, func()) { + return context.WithTimeout(context.Background(), 30*time.Second) +} + +func (c *Config) connectContextMaker() (context.Context, func()) { + if c.ConnectContextMaker == nil { + return defaultConnectContextMaker() + } + return c.ConnectContextMaker() +} + +const defaultMTU = 1200 // bytes + +// 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 { + 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 { + switch cert.PrivateKey.(type) { + case ed25519.PrivateKey: + case *ecdsa.PrivateKey: + case *rsa.PrivateKey: + default: + return errInvalidPrivateKey + } + } + } + + _, err := parseCipherSuites(config.CipherSuites, config.CustomCipherSuites, config.PSK == nil || len(config.Certificates) > 0, config.PSK != nil) + return err +} diff --git a/dtls-2.0.9/config_test.go b/dtls-2.0.9/config_test.go new file mode 100644 index 0000000..a5a0772 --- /dev/null +++ b/dtls-2.0.9/config_test.go @@ -0,0 +1,119 @@ +package dtls + +import ( + "crypto/dsa" //nolint + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "errors" + "testing" + + "github.com/pion/dtls/v2/pkg/crypto/selfsign" +) + +func TestValidateConfig(t *testing.T) { + // Empty config + if err := validateConfig(nil); !errors.Is(err, errNoConfigProvided) { + t.Fatalf("TestValidateConfig: Config validation error exp(%v) failed(%v)", errNoConfigProvided, err) + } + + // PSK and Certificate, valid cipher suites + cert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatalf("TestValidateConfig: Config validation error(%v), self signed certificate not generated", err) + return + } + config := &Config{ + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + PSK: func(hint []byte) ([]byte, error) { + return nil, nil + }, + Certificates: []tls.Certificate{cert}, + } + if err = validateConfig(config); err != nil { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", nil, err) + } + + // PSK and Certificate, no PSK cipher suite + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + PSK: func(hint []byte) ([]byte, error) { + return nil, nil + }, + Certificates: []tls.Certificate{cert}, + } + if err = validateConfig(config); !errors.Is(errNoAvailablePSKCipherSuite, err) { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errNoAvailablePSKCipherSuite, err) + } + + // PSK and Certificate, no non-PSK cipher suite + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8}, + PSK: func(hint []byte) ([]byte, error) { + return nil, nil + }, + Certificates: []tls.Certificate{cert}, + } + if err = validateConfig(config); !errors.Is(errNoAvailableCertificateCipherSuite, err) { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errNoAvailableCertificateCipherSuite, err) + } + + // PSK identity hint with not PSK + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + PSK: nil, + PSKIdentityHint: []byte{}, + } + if err = validateConfig(config); !errors.Is(err, errIdentityNoPSK) { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errIdentityNoPSK, err) + } + + // Invalid private key + dsaPrivateKey := &dsa.PrivateKey{} + err = dsa.GenerateParameters(&dsaPrivateKey.Parameters, rand.Reader, dsa.L1024N160) + if err != nil { + t.Fatalf("TestValidateConfig: Config validation error(%v), DSA parameters not generated", err) + return + } + err = dsa.GenerateKey(dsaPrivateKey, rand.Reader) + if err != nil { + t.Fatalf("TestValidateConfig: Config validation error(%v), DSA private key not generated", err) + return + } + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + Certificates: []tls.Certificate{{Certificate: cert.Certificate, PrivateKey: dsaPrivateKey}}, + } + if err = validateConfig(config); !errors.Is(err, errInvalidPrivateKey) { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errInvalidPrivateKey, err) + } + + // PrivateKey without Certificate + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + Certificates: []tls.Certificate{{PrivateKey: cert.PrivateKey}}, + } + if err = validateConfig(config); !errors.Is(err, errInvalidCertificate) { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", errInvalidCertificate, err) + } + + // Invalid cipher suites + config = &Config{CipherSuites: []CipherSuiteID{0x0000}} + if err = validateConfig(config); err == nil { + t.Fatal("TestValidateConfig: Client error expected with invalid CipherSuiteID") + } + + // Valid config + rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("TestValidateConfig: Config validation error(%v), RSA private key not generated", err) + return + } + config = &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + Certificates: []tls.Certificate{cert, {Certificate: cert.Certificate, PrivateKey: rsaPrivateKey}}, + } + if err = validateConfig(config); err != nil { + t.Fatalf("TestValidateConfig: Client error exp(%v) failed(%v)", nil, err) + } +} diff --git a/dtls-2.0.9/conn.go b/dtls-2.0.9/conn.go new file mode 100644 index 0000000..b44d6ad --- /dev/null +++ b/dtls-2.0.9/conn.go @@ -0,0 +1,979 @@ +package dtls + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/pion/dtls/v2/internal/closer" + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + "github.com/pion/logging" + "github.com/pion/transport/connctx" + "github.com/pion/transport/deadline" + "github.com/pion/transport/replaydetector" +) + +const ( + initialTickerInterval = time.Second + cookieLength = 20 + defaultNamedCurve = elliptic.X25519 + inboundBufferSize = 8192 + // Default replay protection window is specified by RFC 6347 Section 4.1.2.6 + defaultReplayProtectionWindow = 64 +) + +func invalidKeyingLabels() map[string]bool { + return map[string]bool{ + "client finished": true, + "server finished": true, + "master secret": true, + "key expansion": true, + } +} + +// Conn represents a DTLS connection +type Conn struct { + lock sync.RWMutex // Internal lock (must not be public) + nextConn connctx.ConnCtx // Embedded Conn, typically a udpconn we read/write from + fragmentBuffer *fragmentBuffer // out-of-order and missing fragment handling + handshakeCache *handshakeCache // caching of handshake messages for verifyData generation + decrypted chan interface{} // Decrypted Application Data or error, pull by calling `Read` + + state State // Internal state + + maximumTransmissionUnit int + + handshakeCompletedSuccessfully atomic.Value + + encryptedPackets [][]byte + + connectionClosedByUser bool + closeLock sync.Mutex + closed *closer.Closer + handshakeLoopsFinished sync.WaitGroup + + readDeadline *deadline.Deadline + writeDeadline *deadline.Deadline + + log logging.LeveledLogger + + reading chan struct{} + handshakeRecv chan chan struct{} + cancelHandshaker func() + cancelHandshakeReader func() + + fsm *handshakeFSM + + replayProtectionWindow uint +} + +func createConn(ctx context.Context, nextConn net.Conn, config *Config, isClient bool, initialState *State) (*Conn, error) { + err := validateConfig(config) + if err != nil { + return nil, err + } + + if nextConn == nil { + return nil, errNilNextConn + } + + cipherSuites, err := parseCipherSuites(config.CipherSuites, config.CustomCipherSuites, config.PSK == nil || len(config.Certificates) > 0, config.PSK != nil) + if err != nil { + return nil, err + } + + signatureSchemes, err := signaturehash.ParseSignatureSchemes(config.SignatureSchemes, config.InsecureHashes) + if err != nil { + return nil, err + } + + workerInterval := initialTickerInterval + if config.FlightInterval != 0 { + workerInterval = config.FlightInterval + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + logger := loggerFactory.NewLogger("dtls") + + mtu := config.MTU + if mtu <= 0 { + mtu = defaultMTU + } + + replayProtectionWindow := config.ReplayProtectionWindow + if replayProtectionWindow <= 0 { + replayProtectionWindow = defaultReplayProtectionWindow + } + + c := &Conn{ + nextConn: connctx.New(nextConn), + fragmentBuffer: newFragmentBuffer(), + handshakeCache: newHandshakeCache(), + maximumTransmissionUnit: mtu, + + decrypted: make(chan interface{}, 1), + log: logger, + + readDeadline: deadline.New(), + writeDeadline: deadline.New(), + + reading: make(chan struct{}, 1), + handshakeRecv: make(chan chan struct{}), + closed: closer.NewCloser(), + cancelHandshaker: func() {}, + + replayProtectionWindow: uint(replayProtectionWindow), + + state: State{ + isClient: isClient, + }, + } + + c.setRemoteEpoch(0) + c.setLocalEpoch(0) + + serverName := config.ServerName + // Use host from conn address when serverName is not provided + if isClient && serverName == "" && nextConn.RemoteAddr() != nil { + remoteAddr := nextConn.RemoteAddr().String() + var host string + host, _, err = net.SplitHostPort(remoteAddr) + if err != nil { + serverName = remoteAddr + } else { + serverName = host + } + } + + hsCfg := &handshakeConfig{ + localPSKCallback: config.PSK, + localPSKIdentityHint: config.PSKIdentityHint, + localCiscoCompatCallback: config.CiscoCompat, + localCipherSuites: cipherSuites, + localSignatureSchemes: signatureSchemes, + extendedMasterSecret: config.ExtendedMasterSecret, + localSRTPProtectionProfiles: config.SRTPProtectionProfiles, + serverName: serverName, + clientAuth: config.ClientAuth, + localCertificates: config.Certificates, + insecureSkipVerify: config.InsecureSkipVerify, + verifyPeerCertificate: config.VerifyPeerCertificate, + rootCAs: config.RootCAs, + clientCAs: config.ClientCAs, + customCipherSuites: config.CustomCipherSuites, + retransmitInterval: workerInterval, + log: logger, + initialEpoch: 0, + keyLogWriter: config.KeyLogWriter, + } + + var initialFlight flightVal + var initialFSMState handshakeState + + if initialState != nil { + if c.state.isClient { + initialFlight = flight5 + } else { + initialFlight = flight6 + } + initialFSMState = handshakeFinished + + c.state = *initialState + } else { + if c.state.isClient { + initialFlight = flight1 + } else { + initialFlight = flight0 + } + initialFSMState = handshakePreparing + } + // Do handshake + if err := c.handshake(ctx, hsCfg, initialFlight, initialFSMState); err != nil { + return nil, err + } + + c.log.Trace("Handshake Completed") + + return c, nil +} + +// Dial connects to the given network address and establishes a DTLS connection on top. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use DialWithContext() instead. +func Dial(network string, raddr *net.UDPAddr, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return DialWithContext(ctx, network, raddr, config) +} + +// Client establishes a DTLS connection over an existing connection. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use ClientWithContext() instead. +func Client(conn net.Conn, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return ClientWithContext(ctx, conn, config) +} + +// Server listens for incoming DTLS connections. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use ServerWithContext() instead. +func Server(conn net.Conn, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return ServerWithContext(ctx, conn, config) +} + +// DialWithContext connects to the given network address and establishes a DTLS connection on top. +func DialWithContext(ctx context.Context, network string, raddr *net.UDPAddr, config *Config) (*Conn, error) { + pConn, err := net.DialUDP(network, nil, raddr) + if err != nil { + return nil, err + } + return ClientWithContext(ctx, pConn, config) +} + +// ClientWithContext establishes a DTLS connection over an existing connection. +func ClientWithContext(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { + switch { + case config == nil: + return nil, errNoConfigProvided + case config.PSK != nil && config.PSKIdentityHint == nil: + return nil, errPSKAndIdentityMustBeSetForClient + } + + return createConn(ctx, conn, config, true, nil) +} + +// ServerWithContext listens for incoming DTLS connections. +func ServerWithContext(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { + if config == nil { + return nil, errNoConfigProvided + } + + return createConn(ctx, conn, config, false, nil) +} + +// Read reads data from the connection. +func (c *Conn) Read(p []byte) (n int, err error) { + if !c.isHandshakeCompletedSuccessfully() { + return 0, errHandshakeInProgress + } + + select { + case <-c.readDeadline.Done(): + return 0, errDeadlineExceeded + default: + } + + for { + select { + case <-c.readDeadline.Done(): + return 0, errDeadlineExceeded + case out, ok := <-c.decrypted: + if !ok { + return 0, io.EOF + } + switch val := out.(type) { + case ([]byte): + if len(p) < len(val) { + return 0, errBufferTooSmall + } + copy(p, val) + return len(val), nil + case (error): + return 0, val + } + } + } +} + +// Write writes len(p) bytes from p to the DTLS connection +func (c *Conn) Write(p []byte) (int, error) { + if c.isConnectionClosed() { + return 0, ErrConnClosed + } + + select { + case <-c.writeDeadline.Done(): + return 0, errDeadlineExceeded + default: + } + + if !c.isHandshakeCompletedSuccessfully() { + return 0, errHandshakeInProgress + } + + return len(p), c.writePackets(c.writeDeadline, []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Epoch: c.getLocalEpoch(), + Version: protocol.Version1_2, + }, + Content: &protocol.ApplicationData{ + Data: p, + }, + }, + shouldEncrypt: true, + }, + }) +} + +// Close closes the connection. +func (c *Conn) Close() error { + err := c.close(true) + c.handshakeLoopsFinished.Wait() + return err +} + +// ConnectionState returns basic DTLS details about the connection. +// Note that this replaced the `Export` function of v1. +func (c *Conn) ConnectionState() State { + c.lock.RLock() + defer c.lock.RUnlock() + return *c.state.clone() +} + +// SelectedSRTPProtectionProfile returns the selected SRTPProtectionProfile +func (c *Conn) SelectedSRTPProtectionProfile() (SRTPProtectionProfile, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + if c.state.srtpProtectionProfile == 0 { + return 0, false + } + + return c.state.srtpProtectionProfile, true +} + +func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { + c.lock.Lock() + defer c.lock.Unlock() + + var rawPackets [][]byte + + for _, p := range pkts { + if h, ok := p.record.Content.(*handshake.Handshake); ok { + handshakeRaw, err := p.record.Marshal() + if err != nil { + return err + } + + c.log.Tracef("[handshake:%v] -> %s (epoch: %d, seq: %d)", + srvCliStr(c.state.isClient), h.Header.Type.String(), + p.record.Header.Epoch, h.Header.MessageSequence) + c.handshakeCache.push(handshakeRaw[recordlayer.HeaderSize:], p.record.Header.Epoch, h.Header.MessageSequence, h.Header.Type, c.state.isClient) + + rawHandshakePackets, err := c.processHandshakePacket(p, h) + if err != nil { + return err + } + rawPackets = append(rawPackets, rawHandshakePackets...) + } else { + rawPacket, err := c.processPacket(p) + if err != nil { + return err + } + rawPackets = append(rawPackets, rawPacket) + } + } + if len(rawPackets) == 0 { + return nil + } + compactedRawPackets := c.compactRawPackets(rawPackets) + + for _, compactedRawPackets := range compactedRawPackets { + if _, err := c.nextConn.WriteContext(ctx, compactedRawPackets); err != nil { + return netError(err) + } + } + + return nil +} + +func (c *Conn) compactRawPackets(rawPackets [][]byte) [][]byte { + combinedRawPackets := make([][]byte, 0) + currentCombinedRawPacket := make([]byte, 0) + + for _, rawPacket := range rawPackets { + if len(currentCombinedRawPacket) > 0 && len(currentCombinedRawPacket)+len(rawPacket) >= c.maximumTransmissionUnit { + combinedRawPackets = append(combinedRawPackets, currentCombinedRawPacket) + currentCombinedRawPacket = []byte{} + } + currentCombinedRawPacket = append(currentCombinedRawPacket, rawPacket...) + } + + combinedRawPackets = append(combinedRawPackets, currentCombinedRawPacket) + + return combinedRawPackets +} + +func (c *Conn) processPacket(p *packet) ([]byte, error) { + epoch := p.record.Header.Epoch + for len(c.state.localSequenceNumber) <= int(epoch) { + c.state.localSequenceNumber = append(c.state.localSequenceNumber, uint64(0)) + } + seq := atomic.AddUint64(&c.state.localSequenceNumber[epoch], 1) - 1 + if seq > recordlayer.MaxSequenceNumber { + // RFC 6347 Section 4.1.0 + // The implementation must either abandon an association or rehandshake + // prior to allowing the sequence number to wrap. + return nil, errSequenceNumberOverflow + } + p.record.Header.SequenceNumber = seq + + rawPacket, err := p.record.Marshal() + if err != nil { + return nil, err + } + + if p.shouldEncrypt { + var err error + rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket) + if err != nil { + return nil, err + } + } + + return rawPacket, nil +} + +func (c *Conn) processHandshakePacket(p *packet, h *handshake.Handshake) ([][]byte, error) { + rawPackets := make([][]byte, 0) + + handshakeFragments, err := c.fragmentHandshake(h) + if err != nil { + return nil, err + } + epoch := p.record.Header.Epoch + for len(c.state.localSequenceNumber) <= int(epoch) { + c.state.localSequenceNumber = append(c.state.localSequenceNumber, uint64(0)) + } + + for _, handshakeFragment := range handshakeFragments { + seq := atomic.AddUint64(&c.state.localSequenceNumber[epoch], 1) - 1 + if seq > recordlayer.MaxSequenceNumber { + return nil, errSequenceNumberOverflow + } + + recordlayerHeader := &recordlayer.Header{ + Version: p.record.Header.Version, + ContentType: p.record.Header.ContentType, + ContentLen: uint16(len(handshakeFragment)), + Epoch: p.record.Header.Epoch, + SequenceNumber: seq, + } + + recordlayerHeaderBytes, err := recordlayerHeader.Marshal() + if err != nil { + return nil, err + } + + p.record.Header = *recordlayerHeader + + rawPacket := append(recordlayerHeaderBytes, handshakeFragment...) + if p.shouldEncrypt { + var err error + rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket) + if err != nil { + return nil, err + } + } + + rawPackets = append(rawPackets, rawPacket) + } + + return rawPackets, nil +} + +func (c *Conn) fragmentHandshake(h *handshake.Handshake) ([][]byte, error) { + content, err := h.Message.Marshal() + if err != nil { + return nil, err + } + + fragmentedHandshakes := make([][]byte, 0) + + contentFragments := splitBytes(content, c.maximumTransmissionUnit) + if len(contentFragments) == 0 { + contentFragments = [][]byte{ + {}, + } + } + + offset := 0 + for _, contentFragment := range contentFragments { + contentFragmentLen := len(contentFragment) + + headerFragment := &handshake.Header{ + Type: h.Header.Type, + Length: h.Header.Length, + MessageSequence: h.Header.MessageSequence, + FragmentOffset: uint32(offset), + FragmentLength: uint32(contentFragmentLen), + } + + offset += contentFragmentLen + + headerFragmentRaw, err := headerFragment.Marshal() + if err != nil { + return nil, err + } + + fragmentedHandshake := append(headerFragmentRaw, contentFragment...) + fragmentedHandshakes = append(fragmentedHandshakes, fragmentedHandshake) + } + + return fragmentedHandshakes, nil +} + +var poolReadBuffer = sync.Pool{ //nolint:gochecknoglobals + New: func() interface{} { + b := make([]byte, inboundBufferSize) + return &b + }, +} + +func (c *Conn) readAndBuffer(ctx context.Context) error { + bufptr := poolReadBuffer.Get().(*[]byte) + defer poolReadBuffer.Put(bufptr) + + b := *bufptr + i, err := c.nextConn.ReadContext(ctx, b) + if err != nil { + return netError(err) + } + + pkts, err := recordlayer.UnpackDatagram(b[:i]) + if err != nil { + return err + } + + var hasHandshake bool + for _, p := range pkts { + hs, alert, err := c.handleIncomingPacket(p, true) + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err == nil { + err = alertErr + } + } + } + if hs { + hasHandshake = true + } + switch e := err.(type) { + case nil: + case *errAlert: + if e.IsFatalOrCloseNotify() { + return e + } + default: + return e + } + } + if hasHandshake { + done := make(chan struct{}) + select { + case c.handshakeRecv <- done: + // If the other party may retransmit the flight, + // we should respond even if it not a new message. + <-done + case <-c.fsm.Done(): + } + } + return nil +} + +func (c *Conn) handleQueuedPackets(ctx context.Context) error { + pkts := c.encryptedPackets + c.encryptedPackets = nil + + for _, p := range pkts { + _, alert, err := c.handleIncomingPacket(p, false) // don't re-enqueue + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err == nil { + err = alertErr + } + } + } + switch e := err.(type) { + case nil: + case *errAlert: + if e.IsFatalOrCloseNotify() { + return e + } + default: + return e + } + } + return nil +} + +func (c *Conn) handleIncomingPacket(buf []byte, enqueue bool) (bool, *alert.Alert, error) { //nolint:gocognit + h := &recordlayer.Header{} + if err := h.Unmarshal(buf); err != nil { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + c.log.Debugf("discarded broken packet: %v", err) + return false, nil, nil + } + + // Validate epoch + remoteEpoch := c.getRemoteEpoch() + if h.Epoch > remoteEpoch { + if h.Epoch > remoteEpoch+1 { + c.log.Debugf("discarded future packet (epoch: %d, seq: %d)", + h.Epoch, h.SequenceNumber, + ) + return false, nil, nil + } + if enqueue { + c.log.Debug("received packet of next epoch, queuing packet") + c.encryptedPackets = append(c.encryptedPackets, buf) + } + return false, nil, nil + } + + // Anti-replay protection + for len(c.state.replayDetector) <= int(h.Epoch) { + c.state.replayDetector = append(c.state.replayDetector, + replaydetector.New(c.replayProtectionWindow, recordlayer.MaxSequenceNumber), + ) + } + markPacketAsValid, ok := c.state.replayDetector[int(h.Epoch)].Check(h.SequenceNumber) + if !ok { + c.log.Debugf("discarded duplicated packet (epoch: %d, seq: %d)", + h.Epoch, h.SequenceNumber, + ) + return false, nil, nil + } + + // Decrypt + if h.Epoch != 0 { + if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() { + if enqueue { + c.encryptedPackets = append(c.encryptedPackets, buf) + c.log.Debug("handshake not finished, queuing packet") + } + return false, nil, nil + } + + var err error + buf, err = c.state.cipherSuite.Decrypt(buf) + if err != nil { + c.log.Debugf("%s: decrypt failed: %s", srvCliStr(c.state.isClient), err) + return false, nil, nil + } + } + + isHandshake, err := c.fragmentBuffer.push(append([]byte{}, buf...)) + if err != nil { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + c.log.Debugf("defragment failed: %s", err) + return false, nil, nil + } else if isHandshake { + markPacketAsValid() + for out, epoch := c.fragmentBuffer.pop(); out != nil; out, epoch = c.fragmentBuffer.pop() { + rawHandshake := &handshake.Handshake{} + if err := rawHandshake.Unmarshal(out); err != nil { + c.log.Debugf("%s: handshake parse failed: %s", srvCliStr(c.state.isClient), err) + continue + } + + _ = c.handshakeCache.push(out, epoch, rawHandshake.Header.MessageSequence, rawHandshake.Header.Type, !c.state.isClient) + } + + return true, nil, nil + } + + r := &recordlayer.RecordLayer{} + if err := r.Unmarshal(buf); err != nil { + return false, &alert.Alert{Level: alert.Fatal, Description: alert.DecodeError}, err + } + + switch content := r.Content.(type) { + case *alert.Alert: + c.log.Tracef("%s: <- %s", srvCliStr(c.state.isClient), content.String()) + var a *alert.Alert + if content.Description == alert.CloseNotify { + // Respond with a close_notify [RFC5246 Section 7.2.1] + a = &alert.Alert{Level: alert.Warning, Description: alert.CloseNotify} + } + markPacketAsValid() + return false, a, &errAlert{content} + case *protocol.ChangeCipherSpec: + if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() { + if enqueue { + c.encryptedPackets = append(c.encryptedPackets, buf) + c.log.Debugf("CipherSuite not initialized, queuing packet") + } + return false, nil, nil + } + + newRemoteEpoch := h.Epoch + 1 + c.log.Tracef("%s: <- ChangeCipherSpec (epoch: %d)", srvCliStr(c.state.isClient), newRemoteEpoch) + + if c.getRemoteEpoch()+1 == newRemoteEpoch { + c.setRemoteEpoch(newRemoteEpoch) + markPacketAsValid() + } + case *protocol.ApplicationData: + if h.Epoch == 0 { + return false, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, errApplicationDataEpochZero + } + + markPacketAsValid() + + select { + case c.decrypted <- content.Data: + case <-c.closed.Done(): + } + + default: + return false, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, fmt.Errorf("%w: %d", errUnhandledContextType, content.ContentType()) + } + return false, nil, nil +} + +func (c *Conn) recvHandshake() <-chan chan struct{} { + return c.handshakeRecv +} + +func (c *Conn) notify(ctx context.Context, level alert.Level, desc alert.Description) error { + return c.writePackets(ctx, []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Epoch: c.getLocalEpoch(), + Version: protocol.Version1_2, + }, + Content: &alert.Alert{ + Level: level, + Description: desc, + }, + }, + shouldEncrypt: c.isHandshakeCompletedSuccessfully(), + }, + }) +} + +func (c *Conn) setHandshakeCompletedSuccessfully() { + c.handshakeCompletedSuccessfully.Store(struct{ bool }{true}) +} + +func (c *Conn) isHandshakeCompletedSuccessfully() bool { + boolean, _ := c.handshakeCompletedSuccessfully.Load().(struct{ bool }) + return boolean.bool +} + +func (c *Conn) handshake(ctx context.Context, cfg *handshakeConfig, initialFlight flightVal, initialState handshakeState) error { //nolint:gocognit + c.fsm = newHandshakeFSM(&c.state, c.handshakeCache, cfg, initialFlight) + + done := make(chan struct{}) + ctxRead, cancelRead := context.WithCancel(context.Background()) + c.cancelHandshakeReader = cancelRead + cfg.onFlightState = func(f flightVal, s handshakeState) { + if s == handshakeFinished && !c.isHandshakeCompletedSuccessfully() { + c.setHandshakeCompletedSuccessfully() + close(done) + } + } + + ctxHs, cancel := context.WithCancel(context.Background()) + c.cancelHandshaker = cancel + + firstErr := make(chan error, 1) + + c.handshakeLoopsFinished.Add(2) + + // Handshake routine should be live until close. + // The other party may request retransmission of the last flight to cope with packet drop. + go func() { + defer c.handshakeLoopsFinished.Done() + err := c.fsm.Run(ctxHs, c, initialState) + if !errors.Is(err, context.Canceled) { + select { + case firstErr <- err: + default: + } + } + }() + go func() { + defer func() { + // Escaping read loop. + // It's safe to close decrypted channnel now. + close(c.decrypted) + + // Force stop handshaker when the underlying connection is closed. + cancel() + }() + defer c.handshakeLoopsFinished.Done() + for { + if err := c.readAndBuffer(ctxRead); err != nil { + switch e := err.(type) { + case *errAlert: + if !e.IsFatalOrCloseNotify() { + if c.isHandshakeCompletedSuccessfully() { + // Pass the error to Read() + select { + case c.decrypted <- err: + case <-c.closed.Done(): + } + } + continue // non-fatal alert must not stop read loop + } + case error: + switch err { + case context.DeadlineExceeded, context.Canceled, io.EOF: + default: + if c.isHandshakeCompletedSuccessfully() { + // Keep read loop and pass the read error to Read() + select { + case c.decrypted <- err: + case <-c.closed.Done(): + } + continue // non-fatal alert must not stop read loop + } + } + } + select { + case firstErr <- err: + default: + } + + if e, ok := err.(*errAlert); ok { + if e.IsFatalOrCloseNotify() { + _ = c.close(false) + } + } + return + } + } + }() + + select { + case err := <-firstErr: + cancelRead() + cancel() + return c.translateHandshakeCtxError(err) + case <-ctx.Done(): + cancelRead() + cancel() + return c.translateHandshakeCtxError(ctx.Err()) + case <-done: + return nil + } +} + +func (c *Conn) translateHandshakeCtxError(err error) error { + if err == nil { + return nil + } + if errors.Is(err, context.Canceled) && c.isHandshakeCompletedSuccessfully() { + return nil + } + return &HandshakeError{Err: err} +} + +func (c *Conn) close(byUser bool) error { + c.cancelHandshaker() + c.cancelHandshakeReader() + + if c.isHandshakeCompletedSuccessfully() && byUser { + // Discard error from notify() to return non-error on the first user call of Close() + // even if the underlying connection is already closed. + _ = c.notify(context.Background(), alert.Warning, alert.CloseNotify) + } + + c.closeLock.Lock() + // Don't return ErrConnClosed at the first time of the call from user. + closedByUser := c.connectionClosedByUser + if byUser { + c.connectionClosedByUser = true + } + c.closed.Close() + c.closeLock.Unlock() + + if closedByUser { + return ErrConnClosed + } + + return c.nextConn.Close() +} + +func (c *Conn) isConnectionClosed() bool { + select { + case <-c.closed.Done(): + return true + default: + return false + } +} + +func (c *Conn) setLocalEpoch(epoch uint16) { + c.state.localEpoch.Store(epoch) +} + +func (c *Conn) getLocalEpoch() uint16 { + return c.state.localEpoch.Load().(uint16) +} + +func (c *Conn) setRemoteEpoch(epoch uint16) { + c.state.remoteEpoch.Store(epoch) +} + +func (c *Conn) getRemoteEpoch() uint16 { + return c.state.remoteEpoch.Load().(uint16) +} + +// LocalAddr implements net.Conn.LocalAddr +func (c *Conn) LocalAddr() net.Addr { + return c.nextConn.LocalAddr() +} + +// RemoteAddr implements net.Conn.RemoteAddr +func (c *Conn) RemoteAddr() net.Addr { + return c.nextConn.RemoteAddr() +} + +// SetDeadline implements net.Conn.SetDeadline +func (c *Conn) SetDeadline(t time.Time) error { + c.readDeadline.Set(t) + return c.SetWriteDeadline(t) +} + +// SetReadDeadline implements net.Conn.SetReadDeadline +func (c *Conn) SetReadDeadline(t time.Time) error { + c.readDeadline.Set(t) + // Read deadline is fully managed by this layer. + // Don't set read deadline to underlying connection. + return nil +} + +// SetWriteDeadline implements net.Conn.SetWriteDeadline +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline.Set(t) + // Write deadline is also fully managed by this layer. + return nil +} diff --git a/dtls-2.0.9/conn_go_test.go b/dtls-2.0.9/conn_go_test.go new file mode 100644 index 0000000..17a1c62 --- /dev/null +++ b/dtls-2.0.9/conn_go_test.go @@ -0,0 +1,169 @@ +// +build !js + +package dtls + +import ( + "bytes" + "context" + "crypto/tls" + "net" + "testing" + "time" + + "github.com/pion/dtls/v2/internal/net/dpipe" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/transport/test" +) + +func TestContextConfig(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + addrListen, err := net.ResolveUDPAddr("udp", "localhost:0") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Dummy listener + listen, err := net.ListenUDP("udp", addrListen) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer func() { + _ = listen.Close() + }() + addr := listen.LocalAddr().(*net.UDPAddr) + + cert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + config := &Config{ + ConnectContextMaker: func() (context.Context, func()) { + return context.WithTimeout(context.Background(), 40*time.Millisecond) + }, + Certificates: []tls.Certificate{cert}, + } + + dials := map[string]struct { + f func() (func() (net.Conn, error), func()) + order []byte + }{ + "Dial": { + f: func() (func() (net.Conn, error), func()) { + return func() (net.Conn, error) { + return Dial("udp", addr, config) + }, func() { + } + }, + order: []byte{0, 1, 2}, + }, + "DialWithContext": { + f: func() (func() (net.Conn, error), func()) { + ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond) + return func() (net.Conn, error) { + return DialWithContext(ctx, "udp", addr, config) + }, func() { + cancel() + } + }, + order: []byte{0, 2, 1}, + }, + "Client": { + f: func() (func() (net.Conn, error), func()) { + ca, _ := dpipe.Pipe() + return func() (net.Conn, error) { + return Client(ca, config) + }, func() { + _ = ca.Close() + } + }, + order: []byte{0, 1, 2}, + }, + "ClientWithContext": { + f: func() (func() (net.Conn, error), func()) { + ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond) + ca, _ := dpipe.Pipe() + return func() (net.Conn, error) { + return ClientWithContext(ctx, ca, config) + }, func() { + cancel() + _ = ca.Close() + } + }, + order: []byte{0, 2, 1}, + }, + "Server": { + f: func() (func() (net.Conn, error), func()) { + ca, _ := dpipe.Pipe() + return func() (net.Conn, error) { + return Server(ca, config) + }, func() { + _ = ca.Close() + } + }, + order: []byte{0, 1, 2}, + }, + "ServerWithContext": { + f: func() (func() (net.Conn, error), func()) { + ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond) + ca, _ := dpipe.Pipe() + return func() (net.Conn, error) { + return ServerWithContext(ctx, ca, config) + }, func() { + cancel() + _ = ca.Close() + } + }, + order: []byte{0, 2, 1}, + }, + } + + for name, dial := range dials { + dial := dial + t.Run(name, func(t *testing.T) { + done := make(chan struct{}) + + go func() { + d, cancel := dial.f() + conn, err := d() + defer cancel() + if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() { + t.Errorf("Client error exp(Temporary network error) failed(%v)", err) + close(done) + return + } + done <- struct{}{} + if err == nil { + _ = conn.Close() + } + }() + + var order []byte + early := time.After(20 * time.Millisecond) + late := time.After(60 * time.Millisecond) + func() { + for len(order) < 3 { + select { + case <-early: + order = append(order, 0) + case _, ok := <-done: + if !ok { + return + } + order = append(order, 1) + case <-late: + order = append(order, 2) + } + } + }() + if !bytes.Equal(dial.order, order) { + t.Errorf("Invalid cancel timing, expected: %v, got: %v", dial.order, order) + } + }) + } +} diff --git a/dtls-2.0.9/conn_test.go b/dtls-2.0.9/conn_test.go new file mode 100644 index 0000000..b532926 --- /dev/null +++ b/dtls-2.0.9/conn_test.go @@ -0,0 +1,2026 @@ +package dtls + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/pion/dtls/v2/internal/ciphersuite" + "github.com/pion/dtls/v2/internal/net/dpipe" + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/dtls/v2/pkg/crypto/signature" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + "github.com/pion/transport/test" +) + +var ( + errTestPSKInvalidIdentity = errors.New("TestPSK: Server got invalid identity") + errPSKRejected = errors.New("PSK Rejected") + errNotExpectedChain = errors.New("not expected chain") + errExpecedChain = errors.New("expected chain") + errWrongCert = errors.New("wrong cert") +) + +func TestStressDuplex(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + // Run the test + stressDuplex(t) +} + +func stressDuplex(t *testing.T) { + ca, cb, err := pipeMemory() + if err != nil { + t.Fatal(err) + } + + defer func() { + err = ca.Close() + if err != nil { + t.Fatal(err) + } + err = cb.Close() + if err != nil { + t.Fatal(err) + } + }() + + opt := test.Options{ + MsgSize: 2048, + MsgCount: 100, + } + + err = test.StressDuplex(ca, cb, opt) + if err != nil { + t.Fatal(err) + } +} + +func TestRoutineLeakOnClose(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(5 * time.Second) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + ca, cb, err := pipeMemory() + if err != nil { + t.Fatal(err) + } + + if _, err := ca.Write(make([]byte, 100)); err != nil { + t.Fatal(err) + } + if err := cb.Close(); err != nil { + t.Fatal(err) + } + if err := ca.Close(); err != nil { + t.Fatal(err) + } + // Packet is sent, but not read. + // inboundLoop routine should not be leaked. +} + +func TestReadWriteDeadline(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(5 * time.Second) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + ca, cb, err := pipeMemory() + if err != nil { + t.Fatal(err) + } + + if err := ca.SetDeadline(time.Unix(0, 1)); err != nil { + t.Fatal(err) + } + _, werr := ca.Write(make([]byte, 100)) + if e, ok := werr.(net.Error); ok { + if !e.Timeout() { + t.Error("Deadline exceeded Write must return Timeout error") + } + if !e.Temporary() { + t.Error("Deadline exceeded Write must return Temporary error") + } + } else { + t.Error("Write must return net.Error error") + } + _, rerr := ca.Read(make([]byte, 100)) + if e, ok := rerr.(net.Error); ok { + if !e.Timeout() { + t.Error("Deadline exceeded Read must return Timeout error") + } + if !e.Temporary() { + t.Error("Deadline exceeded Read must return Temporary error") + } + } else { + t.Error("Read must return net.Error error") + } + if err := ca.SetDeadline(time.Time{}); err != nil { + t.Error(err) + } + + if err := ca.Close(); err != nil { + t.Error(err) + } + if err := cb.Close(); err != nil { + t.Error(err) + } + + if _, err := ca.Write(make([]byte, 100)); !errors.Is(err, ErrConnClosed) { + t.Errorf("Write must return %v after close, got %v", ErrConnClosed, err) + } + if _, err := ca.Read(make([]byte, 100)); !errors.Is(err, io.EOF) { + t.Errorf("Read must return %v after close, got %v", io.EOF, err) + } +} + +func TestSequenceNumberOverflow(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(5 * time.Second) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + t.Run("ApplicationData", func(t *testing.T) { + ca, cb, err := pipeMemory() + if err != nil { + t.Fatal(err) + } + + atomic.StoreUint64(&ca.state.localSequenceNumber[1], recordlayer.MaxSequenceNumber) + if _, werr := ca.Write(make([]byte, 100)); werr != nil { + t.Errorf("Write must send message with maximum sequence number, but errord: %v", werr) + } + if _, werr := ca.Write(make([]byte, 100)); !errors.Is(werr, errSequenceNumberOverflow) { + t.Errorf("Write must abandonsend message with maximum sequence number, but errord: %v", werr) + } + + if err := ca.Close(); err != nil { + t.Error(err) + } + if err := cb.Close(); err != nil { + t.Error(err) + } + }) + t.Run("Handshake", func(t *testing.T) { + ca, cb, err := pipeMemory() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + atomic.StoreUint64(&ca.state.localSequenceNumber[0], recordlayer.MaxSequenceNumber+1) + + // Try to send handshake packet. + if werr := ca.writePackets(ctx, []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: make([]byte, 64), + CipherSuiteIDs: cipherSuiteIDs(defaultCipherSuites()), + CompressionMethods: defaultCompressionMethods(), + }, + }, + }, + }, + }); !errors.Is(werr, errSequenceNumberOverflow) { + t.Errorf("Connection must fail on handshake packet reaches maximum sequence number") + } + + if err := ca.Close(); err != nil { + t.Error(err) + } + if err := cb.Close(); err != nil { + t.Error(err) + } + }) +} + +func pipeMemory() (*Conn, *Conn, error) { + // In memory pipe + ca, cb := dpipe.Pipe() + return pipeConn(ca, cb) +} + +func pipeConn(ca, cb net.Conn) (*Conn, *Conn, error) { + type result struct { + c *Conn + err error + } + + c := make(chan result) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Setup client + go func() { + client, err := testClient(ctx, ca, &Config{SRTPProtectionProfiles: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}}, true) + c <- result{client, err} + }() + + // Setup server + server, err := testServer(ctx, cb, &Config{SRTPProtectionProfiles: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}}, true) + if err != nil { + return nil, nil, err + } + + // Receive client + res := <-c + if res.err != nil { + return nil, nil, res.err + } + + return res.c, server, nil +} + +func testClient(ctx context.Context, c net.Conn, cfg *Config, generateCertificate bool) (*Conn, error) { + if generateCertificate { + clientCert, err := selfsign.GenerateSelfSigned() + if err != nil { + return nil, err + } + cfg.Certificates = []tls.Certificate{clientCert} + } + cfg.InsecureSkipVerify = true + return ClientWithContext(ctx, c, cfg) +} + +func testServer(ctx context.Context, c net.Conn, cfg *Config, generateCertificate bool) (*Conn, error) { + if generateCertificate { + serverCert, err := selfsign.GenerateSelfSigned() + if err != nil { + return nil, err + } + cfg.Certificates = []tls.Certificate{serverCert} + } + return ServerWithContext(ctx, c, cfg) +} + +func TestHandshakeWithAlert(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cases := map[string]struct { + configServer, configClient *Config + errServer, errClient error + }{ + "CipherSuiteNoIntersection": { + configServer: &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + }, + configClient: &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + }, + errServer: errCipherSuiteNoIntersection, + errClient: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + }, + "SignatureSchemesNoIntersection": { + configServer: &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP256AndSHA256}, + }, + configClient: &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP521AndSHA512}, + }, + errServer: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + errClient: errNoAvailableSignatureSchemes, + }, + } + + for name, testCase := range cases { + testCase := testCase + t.Run(name, func(t *testing.T) { + clientErr := make(chan error, 1) + + ca, cb := dpipe.Pipe() + go func() { + _, err := testClient(ctx, ca, testCase.configClient, true) + clientErr <- err + }() + + _, errServer := testServer(ctx, cb, testCase.configServer, true) + if !errors.Is(errServer, testCase.errServer) { + t.Fatalf("Server error exp(%v) failed(%v)", testCase.errServer, errServer) + } + + errClient := <-clientErr + if !errors.Is(errClient, testCase.errClient) { + t.Fatalf("Client error exp(%v) failed(%v)", testCase.errClient, errClient) + } + }) + } +} + +func TestExportKeyingMaterial(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + var rand [28]byte + exportLabel := "EXTRACTOR-dtls_srtp" + + expectedServerKey := []byte{0x61, 0x09, 0x9d, 0x7d, 0xcb, 0x08, 0x52, 0x2c, 0xe7, 0x7b} + expectedClientKey := []byte{0x87, 0xf0, 0x40, 0x02, 0xf6, 0x1c, 0xf1, 0xfe, 0x8c, 0x77} + + c := &Conn{ + state: State{ + localRandom: handshake.Random{GMTUnixTime: time.Unix(500, 0), RandomBytes: rand}, + remoteRandom: handshake.Random{GMTUnixTime: time.Unix(1000, 0), RandomBytes: rand}, + localSequenceNumber: []uint64{0, 0}, + cipherSuite: &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}, + }, + } + c.setLocalEpoch(0) + c.setRemoteEpoch(0) + + state := c.ConnectionState() + _, err := state.ExportKeyingMaterial(exportLabel, nil, 0) + if !errors.Is(err, errHandshakeInProgress) { + t.Errorf("ExportKeyingMaterial when epoch == 0: expected '%s' actual '%s'", errHandshakeInProgress, err) + } + + c.setLocalEpoch(1) + state = c.ConnectionState() + _, err = state.ExportKeyingMaterial(exportLabel, []byte{0x00}, 0) + if !errors.Is(err, errContextUnsupported) { + t.Errorf("ExportKeyingMaterial with context: expected '%s' actual '%s'", errContextUnsupported, err) + } + + for k := range invalidKeyingLabels() { + state = c.ConnectionState() + _, err = state.ExportKeyingMaterial(k, nil, 0) + if !errors.Is(err, errReservedExportKeyingMaterial) { + t.Errorf("ExportKeyingMaterial reserved label: expected '%s' actual '%s'", errReservedExportKeyingMaterial, err) + } + } + + state = c.ConnectionState() + keyingMaterial, err := state.ExportKeyingMaterial(exportLabel, nil, 10) + if err != nil { + t.Errorf("ExportKeyingMaterial as server: unexpected error '%s'", err) + } else if !bytes.Equal(keyingMaterial, expectedServerKey) { + t.Errorf("ExportKeyingMaterial client export: expected (% 02x) actual (% 02x)", expectedServerKey, keyingMaterial) + } + + c.state.isClient = true + state = c.ConnectionState() + keyingMaterial, err = state.ExportKeyingMaterial(exportLabel, nil, 10) + if err != nil { + t.Errorf("ExportKeyingMaterial as server: unexpected error '%s'", err) + } else if !bytes.Equal(keyingMaterial, expectedClientKey) { + t.Errorf("ExportKeyingMaterial client export: expected (% 02x) actual (% 02x)", expectedClientKey, keyingMaterial) + } +} + +func TestPSK(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + for _, test := range []struct { + Name string + ServerIdentity []byte + CipherSuites []CipherSuiteID + }{ + { + Name: "Server identity specified", + ServerIdentity: []byte("Test Identity"), + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8}, + }, + { + Name: "Server identity nil", + ServerIdentity: nil, + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8}, + }, + { + Name: "TLS_PSK_WITH_AES_128_CBC_SHA256", + ServerIdentity: nil, + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CBC_SHA256}, + }, + } { + test := test + t.Run(test.Name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + clientIdentity := []byte("Client Identity") + type result struct { + c *Conn + err error + } + clientRes := make(chan result, 1) + + ca, cb := dpipe.Pipe() + go func() { + conf := &Config{ + PSK: func(hint []byte) ([]byte, error) { + if !bytes.Equal(test.ServerIdentity, hint) { // nolint + return nil, fmt.Errorf("TestPSK: Client got invalid identity expected(% 02x) actual(% 02x)", test.ServerIdentity, hint) // nolint + } + + return []byte{0xAB, 0xC1, 0x23}, nil + }, + PSKIdentityHint: clientIdentity, + CipherSuites: test.CipherSuites, + } + + c, err := testClient(ctx, ca, conf, false) + clientRes <- result{c, err} + }() + + config := &Config{ + PSK: func(hint []byte) ([]byte, error) { + if !bytes.Equal(clientIdentity, hint) { + return nil, fmt.Errorf("%w: expected(% 02x) actual(% 02x)", errTestPSKInvalidIdentity, clientIdentity, hint) + } + return []byte{0xAB, 0xC1, 0x23}, nil + }, + PSKIdentityHint: test.ServerIdentity, + CipherSuites: test.CipherSuites, + } + + server, err := testServer(ctx, cb, config, false) + if err != nil { + t.Fatalf("TestPSK: Server failed(%v)", err) + } + + actualPSKIdentityHint := server.ConnectionState().IdentityHint + if !bytes.Equal(actualPSKIdentityHint, clientIdentity) { + t.Errorf("TestPSK: Server ClientPSKIdentity Mismatch '%s': expected(%v) actual(%v)", test.Name, clientIdentity, actualPSKIdentityHint) + } + + defer func() { + _ = server.Close() + }() + + res := <-clientRes + if res.err != nil { + t.Fatal(res.err) + } + _ = res.c.Close() + }) + } +} + +func TestPSKHintFail(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + serverAlertError := &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InternalError}} + pskRejected := errPSKRejected + + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + clientErr := make(chan error, 1) + + ca, cb := dpipe.Pipe() + go func() { + conf := &Config{ + PSK: func(hint []byte) ([]byte, error) { + return nil, pskRejected + }, + PSKIdentityHint: []byte{}, + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8}, + } + + _, err := testClient(ctx, ca, conf, false) + clientErr <- err + }() + + config := &Config{ + PSK: func(hint []byte) ([]byte, error) { + return nil, pskRejected + }, + PSKIdentityHint: []byte{}, + CipherSuites: []CipherSuiteID{TLS_PSK_WITH_AES_128_CCM_8}, + } + + if _, err := testServer(ctx, cb, config, false); !errors.Is(err, serverAlertError) { + t.Fatalf("TestPSK: Server error exp(%v) failed(%v)", serverAlertError, err) + } + + if err := <-clientErr; !errors.Is(err, pskRejected) { + t.Fatalf("TestPSK: Client error exp(%v) failed(%v)", pskRejected, err) + } +} + +func TestClientTimeout(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + clientErr := make(chan error, 1) + + ca, _ := dpipe.Pipe() + go func() { + conf := &Config{} + + c, err := testClient(ctx, ca, conf, true) + if err == nil { + _ = c.Close() + } + clientErr <- err + }() + + // no server! + err := <-clientErr + if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() { + t.Fatalf("Client error exp(Temporary network error) failed(%v)", err) + } +} + +func TestSRTPConfiguration(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + for _, test := range []struct { + Name string + ClientSRTP []SRTPProtectionProfile + ServerSRTP []SRTPProtectionProfile + ExpectedProfile SRTPProtectionProfile + WantClientError error + WantServerError error + }{ + { + Name: "No SRTP in use", + ClientSRTP: nil, + ServerSRTP: nil, + ExpectedProfile: 0, + WantClientError: nil, + WantServerError: nil, + }, + { + Name: "SRTP both ends", + ClientSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}, + ServerSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}, + ExpectedProfile: SRTP_AES128_CM_HMAC_SHA1_80, + WantClientError: nil, + WantServerError: nil, + }, + { + Name: "SRTP client only", + ClientSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}, + ServerSRTP: nil, + ExpectedProfile: 0, + WantClientError: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + WantServerError: errServerNoMatchingSRTPProfile, + }, + { + Name: "SRTP server only", + ClientSRTP: nil, + ServerSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80}, + ExpectedProfile: 0, + WantClientError: nil, + WantServerError: nil, + }, + { + Name: "Multiple Suites", + ClientSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32}, + ServerSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32}, + ExpectedProfile: SRTP_AES128_CM_HMAC_SHA1_80, + WantClientError: nil, + WantServerError: nil, + }, + { + Name: "Multiple Suites, Client Chooses", + ClientSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32}, + ServerSRTP: []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_32, SRTP_AES128_CM_HMAC_SHA1_80}, + ExpectedProfile: SRTP_AES128_CM_HMAC_SHA1_80, + WantClientError: nil, + WantServerError: nil, + }, + } { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + client, err := testClient(ctx, ca, &Config{SRTPProtectionProfiles: test.ClientSRTP}, true) + c <- result{client, err} + }() + + server, err := testServer(ctx, cb, &Config{SRTPProtectionProfiles: test.ServerSRTP}, true) + if !errors.Is(err, test.WantServerError) { + t.Errorf("TestSRTPConfiguration: Server Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantServerError, err) + } + if err == nil { + defer func() { + _ = server.Close() + }() + } + + res := <-c + if res.err == nil { + defer func() { + _ = res.c.Close() + }() + } + if !errors.Is(res.err, test.WantClientError) { + t.Fatalf("TestSRTPConfiguration: Client Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantClientError, res.err) + } + if res.c == nil { + return + } + + actualClientSRTP, _ := res.c.SelectedSRTPProtectionProfile() + if actualClientSRTP != test.ExpectedProfile { + t.Errorf("TestSRTPConfiguration: Client SRTPProtectionProfile Mismatch '%s': expected(%v) actual(%v)", test.Name, test.ExpectedProfile, actualClientSRTP) + } + + actualServerSRTP, _ := server.SelectedSRTPProtectionProfile() + if actualServerSRTP != test.ExpectedProfile { + t.Errorf("TestSRTPConfiguration: Server SRTPProtectionProfile Mismatch '%s': expected(%v) actual(%v)", test.Name, test.ExpectedProfile, actualServerSRTP) + } + } +} + +func TestClientCertificate(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + srvCert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + srvCAPool := x509.NewCertPool() + srvCertificate, err := x509.ParseCertificate(srvCert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + srvCAPool.AddCert(srvCertificate) + + cert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + certificate, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + caPool := x509.NewCertPool() + caPool.AddCert(certificate) + + t.Run("parallel", func(t *testing.T) { // sync routines to check routine leak + tests := map[string]struct { + clientCfg *Config + serverCfg *Config + wantErr bool + }{ + "NoClientCert": { + clientCfg: &Config{RootCAs: srvCAPool}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: NoClientCert, + ClientCAs: caPool, + }, + }, + "NoClientCert_cert": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequireAnyClientCert, + }, + }, + "RequestClientCert_cert": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequestClientCert, + }, + }, + "RequestClientCert_no_cert": { + clientCfg: &Config{RootCAs: srvCAPool}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequestClientCert, + ClientCAs: caPool, + }, + }, + "RequireAnyClientCert": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequireAnyClientCert, + }, + }, + "RequireAnyClientCert_error": { + clientCfg: &Config{RootCAs: srvCAPool}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequireAnyClientCert, + }, + wantErr: true, + }, + "VerifyClientCertIfGiven_no_cert": { + clientCfg: &Config{RootCAs: srvCAPool}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: VerifyClientCertIfGiven, + ClientCAs: caPool, + }, + }, + "VerifyClientCertIfGiven_cert": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: VerifyClientCertIfGiven, + ClientCAs: caPool, + }, + }, + "VerifyClientCertIfGiven_error": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: VerifyClientCertIfGiven, + }, + wantErr: true, + }, + "RequireAndVerifyClientCert": { + clientCfg: &Config{RootCAs: srvCAPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ + Certificates: []tls.Certificate{srvCert}, + ClientAuth: RequireAndVerifyClientCert, + ClientCAs: caPool, + }, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + client, err := Client(ca, tt.clientCfg) + c <- result{client, err} + }() + + server, err := Server(cb, tt.serverCfg) + res := <-c + defer func() { + if err == nil { + _ = server.Close() + } + if res.err == nil { + _ = res.c.Close() + } + }() + + if tt.wantErr { + if err != nil { + // Error expected, test succeeded + return + } + t.Error("Error expected") + } + if err != nil { + t.Errorf("Server failed(%v)", err) + } + + if res.err != nil { + t.Errorf("Client failed(%v)", res.err) + } + + actualClientCert := server.ConnectionState().PeerCertificates + if tt.serverCfg.ClientAuth == RequireAnyClientCert || tt.serverCfg.ClientAuth == RequireAndVerifyClientCert { + if actualClientCert == nil { + t.Errorf("Client did not provide a certificate") + } + + if len(actualClientCert) != len(tt.clientCfg.Certificates[0].Certificate) || !bytes.Equal(tt.clientCfg.Certificates[0].Certificate[0], actualClientCert[0]) { + t.Errorf("Client certificate was not communicated correctly") + } + } + if tt.serverCfg.ClientAuth == NoClientCert { + if actualClientCert != nil { + t.Errorf("Client certificate wasn't expected") + } + } + + actualServerCert := res.c.ConnectionState().PeerCertificates + if actualServerCert == nil { + t.Errorf("Server did not provide a certificate") + } + + if len(actualServerCert) != len(tt.serverCfg.Certificates[0].Certificate) || !bytes.Equal(tt.serverCfg.Certificates[0].Certificate[0], actualServerCert[0]) { + t.Errorf("Server certificate was not communicated correctly") + } + }) + } + }) +} + +func TestExtendedMasterSecret(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + tests := map[string]struct { + clientCfg *Config + serverCfg *Config + expectedClientErr error + expectedServerErr error + }{ + "Request_Request_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Request_Require_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Request_Disable_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Require_Request_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Require_Require_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Require_Disable_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + expectedClientErr: errClientRequiredButNoServerEMS, + expectedServerErr: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + }, + "Disable_Request_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequestExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + "Disable_Require_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: RequireExtendedMasterSecret, + }, + expectedClientErr: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + expectedServerErr: errServerRequiredButNoClientEMS, + }, + "Disable_Disable_ExtendedMasterSecret": { + clientCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + serverCfg: &Config{ + ExtendedMasterSecret: DisableExtendedMasterSecret, + }, + expectedClientErr: nil, + expectedServerErr: nil, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + client, err := testClient(ctx, ca, tt.clientCfg, true) + c <- result{client, err} + }() + + server, err := testServer(ctx, cb, tt.serverCfg, true) + res := <-c + defer func() { + if err == nil { + _ = server.Close() + } + if res.err == nil { + _ = res.c.Close() + } + }() + + if !errors.Is(res.err, tt.expectedClientErr) { + t.Errorf("Client error expected: \"%v\" but got \"%v\"", tt.expectedClientErr, res.err) + } + + if !errors.Is(err, tt.expectedServerErr) { + t.Errorf("Server error expected: \"%v\" but got \"%v\"", tt.expectedServerErr, err) + } + }) + } +} + +func TestServerCertificate(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + cert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + certificate, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + caPool := x509.NewCertPool() + caPool.AddCert(certificate) + + t.Run("parallel", func(t *testing.T) { // sync routines to check routine leak + tests := map[string]struct { + clientCfg *Config + serverCfg *Config + wantErr bool + }{ + "no_ca": { + clientCfg: &Config{}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + wantErr: true, + }, + "good_ca": { + clientCfg: &Config{RootCAs: caPool}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + }, + "no_ca_skip_verify": { + clientCfg: &Config{InsecureSkipVerify: true}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + }, + "good_ca_skip_verify_custom_verify_peer": { + clientCfg: &Config{RootCAs: caPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: RequireAnyClientCert, VerifyPeerCertificate: func(cert [][]byte, chain [][]*x509.Certificate) error { + if len(chain) != 0 { + return errNotExpectedChain + } + return nil + }}, + }, + "good_ca_verify_custom_verify_peer": { + clientCfg: &Config{RootCAs: caPool, Certificates: []tls.Certificate{cert}}, + serverCfg: &Config{ClientCAs: caPool, Certificates: []tls.Certificate{cert}, ClientAuth: RequireAndVerifyClientCert, VerifyPeerCertificate: func(cert [][]byte, chain [][]*x509.Certificate) error { + if len(chain) == 0 { + return errExpecedChain + } + return nil + }}, + }, + "good_ca_custom_verify_peer": { + clientCfg: &Config{ + RootCAs: caPool, + VerifyPeerCertificate: func([][]byte, [][]*x509.Certificate) error { + return errWrongCert + }, + }, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + wantErr: true, + }, + "server_name": { + clientCfg: &Config{RootCAs: caPool, ServerName: certificate.Subject.CommonName}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + }, + "server_name_error": { + clientCfg: &Config{RootCAs: caPool, ServerName: "barfoo"}, + serverCfg: &Config{Certificates: []tls.Certificate{cert}, ClientAuth: NoClientCert}, + wantErr: true, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + ca, cb := dpipe.Pipe() + + type result struct { + c *Conn + err error + } + srvCh := make(chan result) + go func() { + s, err := Server(cb, tt.serverCfg) + srvCh <- result{s, err} + }() + + cli, err := Client(ca, tt.clientCfg) + if err == nil { + _ = cli.Close() + } + if !tt.wantErr && err != nil { + t.Errorf("Client failed(%v)", err) + } + if tt.wantErr && err == nil { + t.Fatal("Error expected") + } + + srv := <-srvCh + if srv.err == nil { + _ = srv.c.Close() + } + }) + } + }) +} + +func TestCipherSuiteConfiguration(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + for _, test := range []struct { + Name string + ClientCipherSuites []CipherSuiteID + ServerCipherSuites []CipherSuiteID + WantClientError error + WantServerError error + WantSelectedCipherSuite CipherSuiteID + }{ + { + Name: "No CipherSuites specified", + ClientCipherSuites: nil, + ServerCipherSuites: nil, + WantClientError: nil, + WantServerError: nil, + }, + { + Name: "Invalid CipherSuite", + ClientCipherSuites: []CipherSuiteID{0x00}, + ServerCipherSuites: []CipherSuiteID{0x00}, + WantClientError: &invalidCipherSuite{0x00}, + WantServerError: &invalidCipherSuite{0x00}, + }, + { + Name: "Valid CipherSuites specified", + ClientCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + ServerCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + WantClientError: nil, + WantServerError: nil, + WantSelectedCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, + { + Name: "CipherSuites mismatch", + ClientCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + ServerCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + WantClientError: &errAlert{&alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}}, + WantServerError: errCipherSuiteNoIntersection, + }, + { + Name: "Valid CipherSuites CCM specified", + ClientCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_CCM}, + ServerCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_CCM}, + WantClientError: nil, + WantServerError: nil, + WantSelectedCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + }, + { + Name: "Valid CipherSuites CCM-8 specified", + ClientCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8}, + ServerCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8}, + WantClientError: nil, + WantServerError: nil, + WantSelectedCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + }, + { + Name: "Server supports subset of client suites", + ClientCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + ServerCipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + WantClientError: nil, + WantServerError: nil, + WantSelectedCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + }, + } { + test := test + t.Run(test.Name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + client, err := testClient(ctx, ca, &Config{CipherSuites: test.ClientCipherSuites}, true) + c <- result{client, err} + }() + + server, err := testServer(ctx, cb, &Config{CipherSuites: test.ServerCipherSuites}, true) + if err == nil { + defer func() { + _ = server.Close() + }() + } + if !errors.Is(err, test.WantServerError) { + t.Errorf("TestCipherSuiteConfiguration: Server Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantServerError, err) + } + + res := <-c + if res.err == nil { + _ = server.Close() + } + if !errors.Is(res.err, test.WantClientError) { + t.Errorf("TestSRTPConfiguration: Client Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantClientError, res.err) + } + if test.WantSelectedCipherSuite != 0x00 && res.c.state.cipherSuite.ID() != test.WantSelectedCipherSuite { + t.Errorf("TestCipherSuiteConfiguration: Server Selected Bad Cipher Suite '%s': expected(%v) actual(%v)", test.Name, test.WantSelectedCipherSuite, res.c.state.cipherSuite.ID()) + } + }) + } +} + +func TestCertificateAndPSKServer(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + for _, test := range []struct { + Name string + ClientPSK bool + }{ + { + Name: "Client uses PKI", + ClientPSK: false, + }, + { + Name: "Client uses PSK", + ClientPSK: true, + }, + } { + test := test + t.Run(test.Name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + config := &Config{CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}} + if test.ClientPSK { + config.PSK = func([]byte) ([]byte, error) { + return []byte{0x00, 0x01, 0x02}, nil + } + config.PSKIdentityHint = []byte{0x00} + config.CipherSuites = []CipherSuiteID{TLS_PSK_WITH_AES_128_GCM_SHA256} + } + + client, err := testClient(ctx, ca, config, false) + c <- result{client, err} + }() + + config := &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_PSK_WITH_AES_128_GCM_SHA256}, + PSK: func([]byte) ([]byte, error) { + return []byte{0x00, 0x01, 0x02}, nil + }, + } + + server, err := testServer(ctx, cb, config, true) + if err == nil { + defer func() { + _ = server.Close() + }() + } else { + t.Errorf("TestCertificateAndPSKServer: Server Error Mismatch '%s': expected(%v) actual(%v)", test.Name, nil, err) + } + + res := <-c + if res.err == nil { + _ = server.Close() + } else { + t.Errorf("TestCertificateAndPSKServer: Client Error Mismatch '%s': expected(%v) actual(%v)", test.Name, nil, res.err) + } + }) + } +} + +func TestPSKConfiguration(t *testing.T) { + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + for _, test := range []struct { + Name string + ClientHasCertificate bool + ServerHasCertificate bool + ClientPSK PSKCallback + ServerPSK PSKCallback + ClientPSKIdentity []byte + ServerPSKIdentity []byte + WantClientError error + WantServerError error + }{ + { + Name: "PSK and no certificate specified", + ClientHasCertificate: false, + ServerHasCertificate: false, + ClientPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ServerPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ClientPSKIdentity: []byte{0x00}, + ServerPSKIdentity: []byte{0x00}, + WantClientError: errNoAvailablePSKCipherSuite, + WantServerError: errNoAvailablePSKCipherSuite, + }, + { + Name: "PSK and certificate specified", + ClientHasCertificate: true, + ServerHasCertificate: true, + ClientPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ServerPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ClientPSKIdentity: []byte{0x00}, + ServerPSKIdentity: []byte{0x00}, + WantClientError: errNoAvailablePSKCipherSuite, + WantServerError: errNoAvailablePSKCipherSuite, + }, + { + Name: "PSK and no identity specified", + ClientHasCertificate: false, + ServerHasCertificate: false, + ClientPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ServerPSK: func([]byte) ([]byte, error) { return []byte{0x00, 0x01, 0x02}, nil }, + ClientPSKIdentity: nil, + ServerPSKIdentity: nil, + WantClientError: errPSKAndIdentityMustBeSetForClient, + WantServerError: errNoAvailablePSKCipherSuite, + }, + { + Name: "No PSK and identity specified", + ClientHasCertificate: false, + ServerHasCertificate: false, + ClientPSK: nil, + ServerPSK: nil, + ClientPSKIdentity: []byte{0x00}, + ServerPSKIdentity: []byte{0x00}, + WantClientError: errIdentityNoPSK, + WantServerError: errIdentityNoPSK, + }, + } { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ca, cb := dpipe.Pipe() + type result struct { + c *Conn + err error + } + c := make(chan result) + + go func() { + client, err := testClient(ctx, ca, &Config{PSK: test.ClientPSK, PSKIdentityHint: test.ClientPSKIdentity}, test.ClientHasCertificate) + c <- result{client, err} + }() + + _, err := testServer(ctx, cb, &Config{PSK: test.ServerPSK, PSKIdentityHint: test.ServerPSKIdentity}, test.ServerHasCertificate) + if err != nil || test.WantServerError != nil { + if !(err != nil && test.WantServerError != nil && err.Error() == test.WantServerError.Error()) { + t.Fatalf("TestPSKConfiguration: Server Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantServerError, err) + } + } + + res := <-c + if res.err != nil || test.WantClientError != nil { + if !(res.err != nil && test.WantClientError != nil && res.err.Error() == test.WantClientError.Error()) { + t.Fatalf("TestPSKConfiguration: Client Error Mismatch '%s': expected(%v) actual(%v)", test.Name, test.WantClientError, res.err) + } + } + } +} + +func TestServerTimeout(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + cookie := make([]byte, 20) + _, err := rand.Read(cookie) + if err != nil { + t.Fatal(err) + } + + var rand [28]byte + random := handshake.Random{GMTUnixTime: time.Unix(500, 0), RandomBytes: rand} + + cipherSuites := []CipherSuite{ + &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}, + } + + extensions := []extension.Extension{ + &extension.SupportedSignatureAlgorithms{ + SignatureHashAlgorithms: []signaturehash.Algorithm{ + {Hash: hash.SHA256, Signature: signature.ECDSA}, + {Hash: hash.SHA384, Signature: signature.ECDSA}, + {Hash: hash.SHA512, Signature: signature.ECDSA}, + {Hash: hash.SHA256, Signature: signature.RSA}, + {Hash: hash.SHA384, Signature: signature.RSA}, + {Hash: hash.SHA512, Signature: signature.RSA}, + }, + }, + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &extension.SupportedPointFormats{ + PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed}, + }, + } + + record := &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + SequenceNumber: 0, + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + // sequenceNumber and messageSequence line up, may need to be re-evaluated + Header: handshake.Header{ + MessageSequence: 0, + }, + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: cookie, + Random: random, + CipherSuiteIDs: cipherSuiteIDs(cipherSuites), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + } + + packet, err := record.Marshal() + if err != nil { + t.Fatal(err) + } + + ca, cb := dpipe.Pipe() + defer func() { + err := ca.Close() + if err != nil { + t.Fatal(err) + } + }() + + // Client reader + caReadChan := make(chan []byte, 1000) + go func() { + for { + data := make([]byte, 8192) + n, err := ca.Read(data) + if err != nil { + return + } + + caReadChan <- data[:n] + } + }() + + // Start sending ClientHello packets until server responds with first packet + go func() { + for { + select { + case <-time.After(10 * time.Millisecond): + _, err := ca.Write(packet) + if err != nil { + return + } + case <-caReadChan: + // Once we receive the first reply from the server, stop + return + } + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + config := &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + FlightInterval: 100 * time.Millisecond, + } + + _, serverErr := testServer(ctx, cb, config, true) + if netErr, ok := serverErr.(net.Error); !ok || !netErr.Timeout() { + t.Fatalf("Client error exp(Temporary network error) failed(%v)", serverErr) + } + + // Wait a little longer to ensure no additional messages have been sent by the server + time.Sleep(300 * time.Millisecond) + select { + case msg := <-caReadChan: + t.Fatalf("Expected no additional messages from server, got: %+v", msg) + default: + } +} + +func TestProtocolVersionValidation(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + cookie := make([]byte, 20) + if _, err := rand.Read(cookie); err != nil { + t.Fatal(err) + } + + var rand [28]byte + random := handshake.Random{GMTUnixTime: time.Unix(500, 0), RandomBytes: rand} + + localKeypair, err := elliptic.GenerateKeypair(elliptic.X25519) + if err != nil { + t.Fatal(err) + } + + config := &Config{ + CipherSuites: []CipherSuiteID{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + FlightInterval: 100 * time.Millisecond, + } + + t.Run("Server", func(t *testing.T) { + serverCases := map[string]struct { + records []*recordlayer.RecordLayer + }{ + "ClientHelloVersion": { + records: []*recordlayer.RecordLayer{ + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version{Major: 0xfe, Minor: 0xff}, // try to downgrade + Cookie: cookie, + Random: random, + CipherSuiteIDs: []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())}, + CompressionMethods: defaultCompressionMethods(), + }, + }, + }, + }, + }, + "SecondsClientHelloVersion": { + records: []*recordlayer.RecordLayer{ + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: cookie, + Random: random, + CipherSuiteIDs: []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())}, + CompressionMethods: defaultCompressionMethods(), + }, + }, + }, + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: 1, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: 1, + }, + Message: &handshake.MessageClientHello{ + Version: protocol.Version{Major: 0xfe, Minor: 0xff}, // try to downgrade + Cookie: cookie, + Random: random, + CipherSuiteIDs: []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())}, + CompressionMethods: defaultCompressionMethods(), + }, + }, + }, + }, + }, + } + for name, c := range serverCases { + c := c + t.Run(name, func(t *testing.T) { + ca, cb := dpipe.Pipe() + defer func() { + err := ca.Close() + if err != nil { + t.Error(err) + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + var wg sync.WaitGroup + wg.Add(1) + defer wg.Wait() + go func() { + defer wg.Done() + if _, err := testServer(ctx, cb, config, true); !errors.Is(err, errUnsupportedProtocolVersion) { + t.Errorf("Client error exp(%v) failed(%v)", errUnsupportedProtocolVersion, err) + } + }() + + time.Sleep(50 * time.Millisecond) + + resp := make([]byte, 1024) + for _, record := range c.records { + packet, err := record.Marshal() + if err != nil { + t.Fatal(err) + } + if _, werr := ca.Write(packet); werr != nil { + t.Fatal(werr) + } + n, rerr := ca.Read(resp[:cap(resp)]) + if rerr != nil { + t.Fatal(rerr) + } + resp = resp[:n] + } + + h := &recordlayer.Header{} + if err := h.Unmarshal(resp); err != nil { + t.Fatal("Failed to unmarshal response") + } + if h.ContentType != protocol.ContentTypeAlert { + t.Errorf("Peer must return alert to unsupported protocol version") + } + }) + } + }) + + t.Run("Client", func(t *testing.T) { + clientCases := map[string]struct { + records []*recordlayer.RecordLayer + }{ + "ServerHelloVersion": { + records: []*recordlayer.RecordLayer{ + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageHelloVerifyRequest{ + Version: protocol.Version1_2, + Cookie: cookie, + }, + }, + }, + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: 1, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: 1, + }, + Message: &handshake.MessageServerHello{ + Version: protocol.Version{Major: 0xfe, Minor: 0xff}, // try to downgrade + Random: random, + CipherSuiteID: func() *uint16 { id := uint16(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256); return &id }(), + CompressionMethod: defaultCompressionMethods()[0], + }, + }, + }, + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: 2, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: 2, + }, + Message: &handshake.MessageCertificate{}, + }, + }, + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: 3, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: 3, + }, + Message: &handshake.MessageServerKeyExchange{ + EllipticCurveType: elliptic.CurveTypeNamedCurve, + NamedCurve: elliptic.X25519, + PublicKey: localKeypair.PublicKey, + HashAlgorithm: hash.SHA256, + SignatureAlgorithm: signature.ECDSA, + Signature: make([]byte, 64), + }, + }, + }, + { + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: 4, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: 4, + }, + Message: &handshake.MessageServerHelloDone{}, + }, + }, + }, + }, + } + for name, c := range clientCases { + c := c + t.Run(name, func(t *testing.T) { + ca, cb := dpipe.Pipe() + defer func() { + err := ca.Close() + if err != nil { + t.Error(err) + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + var wg sync.WaitGroup + wg.Add(1) + defer wg.Wait() + go func() { + defer wg.Done() + if _, err := testClient(ctx, cb, config, true); !errors.Is(err, errUnsupportedProtocolVersion) { + t.Errorf("Server error exp(%v) failed(%v)", errUnsupportedProtocolVersion, err) + } + }() + + time.Sleep(50 * time.Millisecond) + + for _, record := range c.records { + if _, err := ca.Read(make([]byte, 1024)); err != nil { + t.Fatal(err) + } + + packet, err := record.Marshal() + if err != nil { + t.Fatal(err) + } + if _, err := ca.Write(packet); err != nil { + t.Fatal(err) + } + } + resp := make([]byte, 1024) + n, err := ca.Read(resp) + if err != nil { + t.Fatal(err) + } + resp = resp[:n] + + h := &recordlayer.Header{} + if err := h.Unmarshal(resp); err != nil { + t.Fatal("Failed to unmarshal response") + } + if h.ContentType != protocol.ContentTypeAlert { + t.Errorf("Peer must return alert to unsupported protocol version") + } + }) + } + }) +} + +func TestMultipleHelloVerifyRequest(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + cookies := [][]byte{ + // first clientHello contains an empty cookie + {}, + } + var packets [][]byte + for i := 0; i < 2; i++ { + cookie := make([]byte, 20) + if _, err := rand.Read(cookie); err != nil { + t.Fatal(err) + } + cookies = append(cookies, cookie) + + record := &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + SequenceNumber: uint64(i), + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: uint16(i), + }, + Message: &handshake.MessageHelloVerifyRequest{ + Version: protocol.Version1_2, + Cookie: cookie, + }, + }, + } + packet, err := record.Marshal() + if err != nil { + t.Fatal(err) + } + packets = append(packets, packet) + } + + ca, cb := dpipe.Pipe() + defer func() { + err := ca.Close() + if err != nil { + t.Error(err) + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + var wg sync.WaitGroup + wg.Add(1) + defer wg.Wait() + go func() { + defer wg.Done() + _, _ = testClient(ctx, ca, &Config{}, false) + }() + + for i, cookie := range cookies { + // read client hello + resp := make([]byte, 1024) + n, err := cb.Read(resp) + if err != nil { + t.Fatal(err) + } + record := &recordlayer.RecordLayer{} + if err := record.Unmarshal(resp[:n]); err != nil { + t.Fatal(err) + } + clientHello := record.Content.(*handshake.Handshake).Message.(*handshake.MessageClientHello) + if !bytes.Equal(clientHello.Cookie, cookie) { + t.Fatalf("Wrong cookie, expected: %x, got: %x", clientHello.Cookie, cookie) + } + if len(packets) <= i { + break + } + // write hello verify request + if _, err := cb.Write(packets[i]); err != nil { + t.Fatal(err) + } + } + cancel() +} + +// Assert that a DTLS Server always responds with RenegotiationInfo if +// a ClientHello contained that extension or not +func TestRenegotationInfo(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(10 * time.Second) + defer lim.Stop() + + // Check for leaking routines + report := test.CheckRoutines(t) + defer report() + + resp := make([]byte, 1024) + + for _, testCase := range []struct { + Name string + SendRenegotiationInfo bool + }{ + { + "Include RenegotiationInfo", + true, + }, + { + "No RenegotiationInfo", + false, + }, + } { + test := testCase + t.Run(test.Name, func(t *testing.T) { + sendClientHello := func(cookie []byte, ca net.Conn, sequenceNumber uint64) { + extensions := []extension.Extension{} + if test.SendRenegotiationInfo { + extensions = append(extensions, &extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }) + } + + packet, err := (&recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + SequenceNumber: sequenceNumber, + }, + Content: &handshake.Handshake{ + Header: handshake.Header{ + MessageSequence: uint16(sequenceNumber), + }, + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: cookie, + CipherSuiteIDs: cipherSuiteIDs(defaultCipherSuites()), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + }).Marshal() + if err != nil { + t.Fatal(err) + } + + if _, err = ca.Write(packet); err != nil { + t.Fatal(err) + } + } + + ca, cb := dpipe.Pipe() + defer func() { + if err := ca.Close(); err != nil { + t.Error(err) + } + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + if _, err := testServer(ctx, cb, &Config{}, true); !errors.Is(err, context.Canceled) { + t.Error(err) + } + }() + + time.Sleep(50 * time.Millisecond) + + sendClientHello([]byte{}, ca, 0) + n, err := ca.Read(resp) + if err != nil { + t.Fatal(err) + } + r := &recordlayer.RecordLayer{} + if err = r.Unmarshal(resp[:n]); err != nil { + t.Fatal(err) + } + + helloVerifyRequest := r.Content.(*handshake.Handshake).Message.(*handshake.MessageHelloVerifyRequest) + + sendClientHello(helloVerifyRequest.Cookie, ca, 1) + if n, err = ca.Read(resp); err != nil { + t.Fatal(err) + } + + messages, err := recordlayer.UnpackDatagram(resp[:n]) + if err != nil { + t.Fatal(err) + } + + if err := r.Unmarshal(messages[0]); err != nil { + t.Fatal(err) + } + + serverHello := r.Content.(*handshake.Handshake).Message.(*handshake.MessageServerHello) + gotNegotationInfo := false + for _, v := range serverHello.Extensions { + if _, ok := v.(*extension.RenegotiationInfo); ok { + gotNegotationInfo = true + } + } + + if !gotNegotationInfo { + t.Fatalf("Received ServerHello without RenegotiationInfo") + } + }) + } +} diff --git a/dtls-2.0.9/crypto.go b/dtls-2.0.9/crypto.go new file mode 100644 index 0000000..768ee47 --- /dev/null +++ b/dtls-2.0.9/crypto.go @@ -0,0 +1,221 @@ +package dtls + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/binary" + "math/big" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/hash" +) + +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 +} + +// 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, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) { + msg := valueKeyMessage(clientRandom, serverRandom, publicKey, namedCurve) + switch p := privateKey.(type) { + case ed25519.PrivateKey: + // https://crypto.stackexchange.com/a/55483 + return p.Sign(rand.Reader, msg, crypto.Hash(0)) + case *ecdsa.PrivateKey: + hashed := hashAlgorithm.Digest(msg) + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + case *rsa.PrivateKey: + hashed := hashAlgorithm.Digest(msg) + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + } + + return nil, errKeySignatureGenerateUnimplemented +} + +func verifyKeySignature(message, remoteKeySignature []byte, hashAlgorithm hash.Algorithm, rawCertificates [][]byte) error { //nolint:dupl + if len(rawCertificates) == 0 { + return errLengthMismatch + } + certificate, err := x509.ParseCertificate(rawCertificates[0]) + if err != nil { + return err + } + + switch p := certificate.PublicKey.(type) { + case ed25519.PublicKey: + if ok := ed25519.Verify(p, 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(p, hashed, ecdsaSig.R, ecdsaSig.S) { + return errKeySignatureMismatch + } + return nil + case *rsa.PublicKey: + switch certificate.SignatureAlgorithm { + case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: + hashed := hashAlgorithm.Digest(message) + return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature) + default: + return errKeySignatureVerifyUnimplemented + } + } + + 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, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) { + h := sha256.New() + if _, err := h.Write(handshakeBodies); err != nil { + return nil, err + } + hashed := h.Sum(nil) + + switch p := privateKey.(type) { + case ed25519.PrivateKey: + // https://crypto.stackexchange.com/a/55483 + return p.Sign(rand.Reader, hashed, crypto.Hash(0)) + case *ecdsa.PrivateKey: + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + case *rsa.PrivateKey: + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + } + + return nil, errInvalidSignatureAlgorithm +} + +func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hash.Algorithm, remoteKeySignature []byte, rawCertificates [][]byte) error { //nolint:dupl + if len(rawCertificates) == 0 { + return errLengthMismatch + } + certificate, err := x509.ParseCertificate(rawCertificates[0]) + if err != nil { + return err + } + + switch p := certificate.PublicKey.(type) { + case ed25519.PublicKey: + if ok := ed25519.Verify(p, 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(p, hash, ecdsaSig.R, ecdsaSig.S) { + return errKeySignatureMismatch + } + return nil + case *rsa.PublicKey: + switch certificate.SignatureAlgorithm { + case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: + hash := hashAlgorithm.Digest(handshakeBodies) + return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hash, remoteKeySignature) + default: + return errKeySignatureVerifyUnimplemented + } + } + + 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) (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}, + } + return certificate[0].Verify(opts) +} + +func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName string) (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, + } + return certificate[0].Verify(opts) +} diff --git a/dtls-2.0.9/crypto_test.go b/dtls-2.0.9/crypto_test.go new file mode 100644 index 0000000..03b714d --- /dev/null +++ b/dtls-2.0.9/crypto_test.go @@ -0,0 +1,73 @@ +package dtls + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "testing" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/hash" +) + +const rawPrivateKey = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxIA2BrrnR2sIlATsp7aRBD/3krwZ7vt9dNeoDQAee0s6SuYP +6MBx/HPnAkwNvPS90R05a7pwRkoT6Ur4PfPhCVlUe8lV+0Eto3ZSEeHz3HdsqlM3 +bso67L7Dqrc7MdVstlKcgJi8yeAoGOIL9/igOv0XBFCeznm9nznx6mnsR5cugw+1 +ypXelaHmBCLV7r5SeVSh57+KhvZGbQ2fFpUaTPegRpJZXBNS8lSeWvtOv9d6N5UB +ROTAJodMZT5AfX0jB0QB9IT/0I96H6BSENH08NXOeXApMuLKvnAf361rS7cRAfRL +rWZqERMP4u6Cnk0Cnckc3WcW27kGGIbtwbqUIQIDAQABAoIBAGF7OVIdZp8Hejn0 +N3L8HvT8xtUEe9kS6ioM0lGgvX5s035Uo4/T6LhUx0VcdXRH9eLHnLTUyN4V4cra +ZkxVsE3zAvZl60G6E+oDyLMWZOP6Wu4kWlub9597A5atT7BpMIVCdmFVZFLB4SJ3 +AXkC3nplFAYP+Lh1rJxRIrIn2g+pEeBboWbYA++oDNuMQffDZaokTkJ8Bn1JZYh0 +xEXKY8Bi2Egd5NMeZa1UFO6y8tUbZfwgVs6Enq5uOgtfayq79vZwyjj1kd29MBUD +8g8byV053ZKxbUOiOuUts97eb+fN3DIDRTcT2c+lXt/4C54M1FclJAbtYRK/qwsl +pYWKQAECgYEA4ZUbqQnTo1ICvj81ifGrz+H4LKQqe92Hbf/W51D/Umk2kP702W22 +HP4CvrJRtALThJIG9m2TwUjl/WAuZIBrhSAbIvc3Fcoa2HjdRp+sO5U1ueDq7d/S +Z+PxRI8cbLbRpEdIaoR46qr/2uWZ943PHMv9h4VHPYn1w8b94hwD6vkCgYEA3v87 +mFLzyM9ercnEv9zHMRlMZFQhlcUGQZvfb8BuJYl/WogyT6vRrUuM0QXULNEPlrin +mBQTqc1nCYbgkFFsD2VVt1qIyiAJsB9MD1LNV6YuvE7T2KOSadmsA4fa9PUqbr71 +hf3lTTq+LeR09LebO7WgSGYY+5YKVOEGpYMR1GkCgYEAxPVQmk3HKHEhjgRYdaG5 +lp9A9ZE8uruYVJWtiHgzBTxx9TV2iST+fd/We7PsHFTfY3+wbpcMDBXfIVRKDVwH +BMwchXH9+Ztlxx34bYJaegd0SmA0Hw9ugWEHNgoSEmWpM1s9wir5/ELjc7dGsFtz +uzvsl9fpdLSxDYgAAdzeGtkCgYBAzKIgrVox7DBzB8KojhtD5ToRnXD0+H/M6OKQ +srZPKhlb0V/tTtxrIx0UUEFLlKSXA6mPw6XDHfDnD86JoV9pSeUSlrhRI+Ysy6tq +eIE7CwthpPZiaYXORHZ7wCqcK/HcpJjsCs9rFbrV0yE5S3FMdIbTAvgXg44VBB7O +UbwIoQKBgDuY8gSrA5/A747wjjmsdRWK4DMTMEV4eCW1BEP7Tg7Cxd5n3xPJiYhr +nhLGN+mMnVIcv2zEMS0/eNZr1j/0BtEdx+3IC6Eq+ONY0anZ4Irt57/5QeKgKn/L +JPhfPySIPG4UmwE4gW8t79vfOKxnUu2fDD1ZXUYopan6EckACNH/ +-----END RSA PRIVATE KEY----- +` + +func TestGenerateKeySignature(t *testing.T) { + block, _ := pem.Decode([]byte(rawPrivateKey)) + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Error(err) + } + + clientRandom := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} + serverRandom := []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f} + publicKey := []byte{0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15} + expectedSignature := []byte{ + 0x6f, 0x47, 0x97, 0x85, 0xcc, 0x76, 0x50, 0x93, 0xbd, 0xe2, 0x6a, 0x69, 0x0b, 0xc3, 0x03, 0xd1, 0xb7, 0xe4, 0xab, 0x88, 0x7b, 0xa6, 0x52, 0x80, 0xdf, + 0xaa, 0x25, 0x7a, 0xdb, 0x29, 0x32, 0xe4, 0xd8, 0x28, 0x28, 0xb3, 0xe8, 0x04, 0x3c, 0x38, 0x16, 0xfc, 0x78, 0xe9, 0x15, 0x7b, 0xc5, 0xbd, 0x7d, 0xfc, + 0xcd, 0x83, 0x00, 0x57, 0x4a, 0x3c, 0x23, 0x85, 0x75, 0x6b, 0x37, 0xd5, 0x89, 0x72, 0x73, 0xf0, 0x44, 0x8c, 0x00, 0x70, 0x1f, 0x6e, 0xa2, 0x81, 0xd0, + 0x09, 0xc5, 0x20, 0x36, 0xab, 0x23, 0x09, 0x40, 0x1f, 0x4d, 0x45, 0x96, 0x62, 0xbb, 0x81, 0xb0, 0x30, 0x72, 0xad, 0x3a, 0x0a, 0xac, 0x31, 0x63, 0x40, + 0x52, 0x0a, 0x27, 0xf3, 0x34, 0xde, 0x27, 0x7d, 0xb7, 0x54, 0xff, 0x0f, 0x9f, 0x5a, 0xfe, 0x07, 0x0f, 0x4e, 0x9f, 0x53, 0x04, 0x34, 0x62, 0xf4, 0x30, + 0x74, 0x83, 0x35, 0xfc, 0xe4, 0x7e, 0xbf, 0x5a, 0xc4, 0x52, 0xd0, 0xea, 0xf9, 0x61, 0x4e, 0xf5, 0x1c, 0x0e, 0x58, 0x02, 0x71, 0xfb, 0x1f, 0x34, 0x55, + 0xe8, 0x36, 0x70, 0x3c, 0xc1, 0xcb, 0xc9, 0xb7, 0xbb, 0xb5, 0x1c, 0x44, 0x9a, 0x6d, 0x88, 0x78, 0x98, 0xd4, 0x91, 0x2e, 0xeb, 0x98, 0x81, 0x23, 0x30, + 0x73, 0x39, 0x43, 0xd5, 0xbb, 0x70, 0x39, 0xba, 0x1f, 0xdb, 0x70, 0x9f, 0x91, 0x83, 0x56, 0xc2, 0xde, 0xed, 0x17, 0x6d, 0x2c, 0x3e, 0x21, 0xea, 0x36, + 0xb4, 0x91, 0xd8, 0x31, 0x05, 0x60, 0x90, 0xfd, 0xc6, 0x74, 0xa9, 0x7b, 0x18, 0xfc, 0x1c, 0x6a, 0x1c, 0x6e, 0xec, 0xd3, 0xc1, 0xc0, 0x0d, 0x11, 0x25, + 0x48, 0x37, 0x3d, 0x45, 0x11, 0xa2, 0x31, 0x14, 0x0a, 0x66, 0x9f, 0xd8, 0xac, 0x74, 0xa2, 0xcd, 0xc8, 0x79, 0xb3, 0x9e, 0xc6, 0x66, 0x25, 0xcf, 0x2c, + 0x87, 0x5e, 0x5c, 0x36, 0x75, 0x86, + } + + signature, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256) + if err != nil { + t.Error(err) + } else if !bytes.Equal(expectedSignature, signature) { + t.Errorf("Signature generation failed \nexp % 02x \nactual % 02x ", expectedSignature, signature) + } +} diff --git a/dtls-2.0.9/dtls.go b/dtls-2.0.9/dtls.go new file mode 100644 index 0000000..125b904 --- /dev/null +++ b/dtls-2.0.9/dtls.go @@ -0,0 +1,2 @@ +// Package dtls implements Datagram Transport Layer Security (DTLS) 1.2 +package dtls diff --git a/dtls-2.0.9/e2e/Dockerfile b/dtls-2.0.9/e2e/Dockerfile new file mode 100644 index 0000000..7166fbc --- /dev/null +++ b/dtls-2.0.9/e2e/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.14-alpine3.11 + +RUN apk add --no-cache \ + openssl + +ENV CGO_ENABLED=0 + +COPY . /go/src/github.com/pion/dtls +WORKDIR /go/src/github.com/pion/dtls/e2e + +CMD ["go", "test", "-tags=openssl", "-v", "."] diff --git a/dtls-2.0.9/e2e/e2e.go b/dtls-2.0.9/e2e/e2e.go new file mode 100644 index 0000000..1a2b024 --- /dev/null +++ b/dtls-2.0.9/e2e/e2e.go @@ -0,0 +1,2 @@ +// Package e2e contains end to end tests for pion/dtls +package e2e diff --git a/dtls-2.0.9/e2e/e2e_lossy_test.go b/dtls-2.0.9/e2e/e2e_lossy_test.go new file mode 100644 index 0000000..92a4074 --- /dev/null +++ b/dtls-2.0.9/e2e/e2e_lossy_test.go @@ -0,0 +1,207 @@ +package e2e + +import ( + "crypto/tls" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + transportTest "github.com/pion/transport/test" +) + +const ( + flightInterval = time.Millisecond * 100 + lossyTestTimeout = 30 * time.Second +) + +/* + DTLS Client/Server over a lossy transport, just asserts it can handle at increasing increments +*/ +func TestPionE2ELossy(t *testing.T) { + // Check for leaking routines + report := transportTest.CheckRoutines(t) + defer report() + + type runResult struct { + dtlsConn *dtls.Conn + err error + } + + serverCert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + + clientCert, err := selfsign.GenerateSelfSigned() + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + LossChanceRange int + DoClientAuth bool + CipherSuites []dtls.CipherSuiteID + MTU int + }{ + { + LossChanceRange: 0, + }, + { + LossChanceRange: 10, + }, + { + LossChanceRange: 20, + }, + { + LossChanceRange: 50, + }, + { + LossChanceRange: 0, + DoClientAuth: true, + }, + { + LossChanceRange: 10, + DoClientAuth: true, + }, + { + LossChanceRange: 20, + DoClientAuth: true, + }, + { + LossChanceRange: 50, + DoClientAuth: true, + }, + { + LossChanceRange: 0, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + }, + { + LossChanceRange: 10, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + }, + { + LossChanceRange: 20, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + }, + { + LossChanceRange: 50, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + }, + { + LossChanceRange: 10, + MTU: 100, + DoClientAuth: true, + }, + { + LossChanceRange: 20, + MTU: 100, + DoClientAuth: true, + }, + { + LossChanceRange: 50, + MTU: 100, + DoClientAuth: true, + }, + } { + name := fmt.Sprintf("Loss%d_MTU%d", test.LossChanceRange, test.MTU) + if test.DoClientAuth { + name += "_WithCliAuth" + } + for _, ciph := range test.CipherSuites { + name += "_With" + ciph.String() + } + test := test + t.Run(name, func(t *testing.T) { + // Limit runtime in case of deadlocks + lim := transportTest.TimeOut(lossyTestTimeout + time.Second) + defer lim.Stop() + + rand.Seed(time.Now().UTC().UnixNano()) + chosenLoss := rand.Intn(9) + test.LossChanceRange //nolint:gosec + serverDone := make(chan runResult) + clientDone := make(chan runResult) + br := transportTest.NewBridge() + + if err = br.SetLossChance(chosenLoss); err != nil { + t.Fatal(err) + } + + go func() { + cfg := &dtls.Config{ + FlightInterval: flightInterval, + CipherSuites: test.CipherSuites, + InsecureSkipVerify: true, + MTU: test.MTU, + } + + if test.DoClientAuth { + cfg.Certificates = []tls.Certificate{clientCert} + } + + client, startupErr := dtls.Client(br.GetConn0(), cfg) + clientDone <- runResult{client, startupErr} + }() + + go func() { + cfg := &dtls.Config{ + Certificates: []tls.Certificate{serverCert}, + FlightInterval: flightInterval, + MTU: test.MTU, + } + + if test.DoClientAuth { + cfg.ClientAuth = dtls.RequireAnyClientCert + } + + server, startupErr := dtls.Server(br.GetConn1(), cfg) + serverDone <- runResult{server, startupErr} + }() + + testTimer := time.NewTimer(lossyTestTimeout) + var serverConn, clientConn *dtls.Conn + defer func() { + if serverConn != nil { + if err = serverConn.Close(); err != nil { + t.Error(err) + } + } + if clientConn != nil { + if err = clientConn.Close(); err != nil { + t.Error(err) + } + } + }() + + for { + if serverConn != nil && clientConn != nil { + break + } + + br.Tick() + select { + case serverResult := <-serverDone: + if serverResult.err != nil { + t.Errorf("Fail, serverError: clientComplete(%t) serverComplete(%t) LossChance(%d) error(%v)", clientConn != nil, serverConn != nil, chosenLoss, serverResult.err) + return + } + + serverConn = serverResult.dtlsConn + case clientResult := <-clientDone: + if clientResult.err != nil { + t.Errorf("Fail, clientError: clientComplete(%t) serverComplete(%t) LossChance(%d) error(%v)", clientConn != nil, serverConn != nil, chosenLoss, clientResult.err) + return + } + + clientConn = clientResult.dtlsConn + case <-testTimer.C: + t.Errorf("Test expired: clientComplete(%t) serverComplete(%t) LossChance(%d)", clientConn != nil, serverConn != nil, chosenLoss) + return + case <-time.After(10 * time.Millisecond): + } + } + }) + } +} diff --git a/dtls-2.0.9/e2e/e2e_openssl_test.go b/dtls-2.0.9/e2e/e2e_openssl_test.go new file mode 100644 index 0000000..fd2e60f --- /dev/null +++ b/dtls-2.0.9/e2e/e2e_openssl_test.go @@ -0,0 +1,250 @@ +// +build openssl,!js + +package e2e + +import ( + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "net" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/pion/dtls/v2" +) + +func serverOpenSSL(c *comm) { + go func() { + c.serverMutex.Lock() + defer c.serverMutex.Unlock() + + cfg := c.serverConfig + + // create openssl arguments + args := []string{ + "s_server", + "-dtls1_2", + "-quiet", + "-verify_quiet", + "-verify_return_error", + fmt.Sprintf("-accept=%d", c.serverPort), + } + ciphers := ciphersOpenSSL(cfg) + if ciphers != "" { + args = append(args, fmt.Sprintf("-cipher=%s", ciphers)) + } + + // psk arguments + if cfg.PSK != nil { + psk, err := cfg.PSK(nil) + if err != nil { + c.errChan <- err + return + } + args = append(args, fmt.Sprintf("-psk=%X", psk)) + if len(cfg.PSKIdentityHint) > 0 { + args = append(args, fmt.Sprintf("-psk_hint=%s", cfg.PSKIdentityHint)) + } + } + + // certs arguments + if len(cfg.Certificates) > 0 { + // create temporary cert files + certPEM, keyPEM, err := writeTempPEM(cfg) + if err != nil { + c.errChan <- err + return + } + args = append(args, + fmt.Sprintf("-cert=%s", certPEM), + fmt.Sprintf("-key=%s", keyPEM)) + defer func() { + _ = os.Remove(certPEM) + _ = os.Remove(keyPEM) + }() + } else { + args = append(args, "-nocert") + } + + // launch command + // #nosec G204 + cmd := exec.CommandContext(c.ctx, "openssl", args...) + var inner net.Conn + inner, c.serverConn = net.Pipe() + cmd.Stdin = inner + cmd.Stdout = inner + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + c.errChan <- err + _ = inner.Close() + return + } + + // Ensure that server has started + time.Sleep(500 * time.Millisecond) + + c.serverReady <- struct{}{} + simpleReadWrite(c.errChan, c.serverChan, c.serverConn, c.messageRecvCount) + }() +} + +func clientOpenSSL(c *comm) { + select { + case <-c.serverReady: + // OK + case <-time.After(time.Second): + c.errChan <- errors.New("waiting on serverReady err: timeout") + } + + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + + cfg := c.clientConfig + + // create openssl arguments + args := []string{ + "s_client", + "-dtls1_2", + "-quiet", + "-verify_quiet", + "-verify_return_error", + "-servername=localhost", + fmt.Sprintf("-connect=127.0.0.1:%d", c.serverPort), + } + ciphers := ciphersOpenSSL(cfg) + if ciphers != "" { + args = append(args, fmt.Sprintf("-cipher=%s", ciphers)) + } + + // psk arguments + if cfg.PSK != nil { + psk, err := cfg.PSK(nil) + if err != nil { + c.errChan <- err + return + } + args = append(args, fmt.Sprintf("-psk=%X", psk)) + } + + // certificate arguments + if len(cfg.Certificates) > 0 { + // create temporary cert files + certPEM, keyPEM, err := writeTempPEM(cfg) + if err != nil { + c.errChan <- err + return + } + args = append(args, fmt.Sprintf("-CAfile=%s", certPEM)) + defer func() { + _ = os.Remove(certPEM) + _ = os.Remove(keyPEM) + }() + } + + // launch command + // #nosec G204 + cmd := exec.CommandContext(c.ctx, "openssl", args...) + var inner net.Conn + inner, c.clientConn = net.Pipe() + cmd.Stdin = inner + cmd.Stdout = inner + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + c.errChan <- err + _ = inner.Close() + return + } + + simpleReadWrite(c.errChan, c.clientChan, c.clientConn, c.messageRecvCount) +} + +func ciphersOpenSSL(cfg *dtls.Config) string { + // See https://tls.mbed.org/supported-ssl-ciphersuites + translate := map[dtls.CipherSuiteID]string{ + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: "ECDHE-ECDSA-AES128-CCM", + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: "ECDHE-ECDSA-AES128-CCM8", + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "ECDHE-ECDSA-AES128-GCM-SHA256", + dtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "ECDHE-RSA-AES128-GCM-SHA256", + + dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "ECDHE-ECDSA-AES256-SHA", + dtls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "ECDHE-RSA-AES128-SHA", + + dtls.TLS_PSK_WITH_AES_128_CCM: "PSK-AES128-CCM", + dtls.TLS_PSK_WITH_AES_128_CCM_8: "PSK-AES128-CCM8", + dtls.TLS_PSK_WITH_AES_128_GCM_SHA256: "PSK-AES128-GCM-SHA256", + } + + var ciphers []string + for _, c := range cfg.CipherSuites { + if text, ok := translate[c]; ok { + ciphers = append(ciphers, text) + } + } + return strings.Join(ciphers, ";") +} + +func writeTempPEM(cfg *dtls.Config) (string, string, error) { + certOut, err := ioutil.TempFile("", "cert.pem") + if err != nil { + return "", "", fmt.Errorf("failed to create temporary file: %w", err) + } + keyOut, err := ioutil.TempFile("", "key.pem") + if err != nil { + return "", "", fmt.Errorf("failed to create temporary file: %w", err) + } + + cert := cfg.Certificates[0] + derBytes := cert.Certificate[0] + if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return "", "", fmt.Errorf("failed to write data to cert.pem: %w", err) + } + if err = certOut.Close(); err != nil { + return "", "", fmt.Errorf("error closing cert.pem: %w", err) + } + + priv := cert.PrivateKey + var privBytes []byte + privBytes, err = x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return "", "", fmt.Errorf("unable to marshal private key: %w", err) + } + if err = pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return "", "", fmt.Errorf("failed to write data to key.pem: %w", err) + } + if err = keyOut.Close(); err != nil { + return "", "", fmt.Errorf("error closing key.pem: %w", err) + } + return certOut.Name(), keyOut.Name(), nil +} + +func TestPionOpenSSLE2ESimple(t *testing.T) { + t.Run("OpenSSLServer", func(t *testing.T) { + testPionE2ESimple(t, serverOpenSSL, clientPion) + }) + t.Run("OpenSSLClient", func(t *testing.T) { + testPionE2ESimple(t, serverPion, clientOpenSSL) + }) +} + +func TestPionOpenSSLE2ESimplePSK(t *testing.T) { + t.Run("OpenSSLServer", func(t *testing.T) { + testPionE2ESimplePSK(t, serverOpenSSL, clientPion) + }) + t.Run("OpenSSLClient", func(t *testing.T) { + testPionE2ESimplePSK(t, serverPion, clientOpenSSL) + }) +} + +func TestPionOpenSSLE2EMTUs(t *testing.T) { + t.Run("OpenSSLServer", func(t *testing.T) { + testPionE2EMTUs(t, serverOpenSSL, clientPion) + }) + t.Run("OpenSSLClient", func(t *testing.T) { + testPionE2EMTUs(t, serverPion, clientOpenSSL) + }) +} diff --git a/dtls-2.0.9/e2e/e2e_openssl_v113_test.go b/dtls-2.0.9/e2e/e2e_openssl_v113_test.go new file mode 100644 index 0000000..1d947b6 --- /dev/null +++ b/dtls-2.0.9/e2e/e2e_openssl_v113_test.go @@ -0,0 +1,17 @@ +// +build openssl,go1.13,!js + +package e2e + +import ( + "testing" +) + +func TestPionOpenSSLE2ESimpleED25519(t *testing.T) { + t.Skip("TODO: waiting OpenSSL's DTLS Ed25519 support") + t.Run("OpenSSLServer", func(t *testing.T) { + testPionE2ESimpleED25519(t, serverOpenSSL, clientPion) + }) + t.Run("OpenSSLClient", func(t *testing.T) { + testPionE2ESimpleED25519(t, serverPion, clientOpenSSL) + }) +} diff --git a/dtls-2.0.9/e2e/e2e_test.go b/dtls-2.0.9/e2e/e2e_test.go new file mode 100644 index 0000000..1a77b3b --- /dev/null +++ b/dtls-2.0.9/e2e/e2e_test.go @@ -0,0 +1,329 @@ +// +build !js + +package e2e + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/transport/test" +) + +const ( + testMessage = "Hello World" + testTimeLimit = 5 * time.Second + messageRetry = 200 * time.Millisecond +) + +var errServerTimeout = errors.New("waiting on serverReady err: timeout") + +func randomPort(t testing.TB) int { + t.Helper() + conn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to pickPort: %v", err) + } + defer func() { + _ = conn.Close() + }() + switch addr := conn.LocalAddr().(type) { + case *net.UDPAddr: + return addr.Port + default: + t.Fatalf("unknown addr type %T", addr) + return 0 + } +} + +func simpleReadWrite(errChan chan error, outChan chan string, conn io.ReadWriter, messageRecvCount *uint64) { + go func() { + buffer := make([]byte, 8192) + n, err := conn.Read(buffer) + if err != nil { + errChan <- err + return + } + + outChan <- string(buffer[:n]) + atomic.AddUint64(messageRecvCount, 1) + }() + + for { + if atomic.LoadUint64(messageRecvCount) == 2 { + break + } else if _, err := conn.Write([]byte(testMessage)); err != nil { + errChan <- err + break + } + + time.Sleep(messageRetry) + } +} + +type comm struct { + ctx context.Context + clientConfig, serverConfig *dtls.Config + serverPort int + messageRecvCount *uint64 // Counter to make sure both sides got a message + clientMutex *sync.Mutex + clientConn net.Conn + serverMutex *sync.Mutex + serverConn net.Conn + serverListener net.Listener + serverReady chan struct{} + errChan chan error + clientChan chan string + serverChan chan string + client func(*comm) + server func(*comm) +} + +func newComm(ctx context.Context, clientConfig, serverConfig *dtls.Config, serverPort int, server, client func(*comm)) *comm { + messageRecvCount := uint64(0) + c := &comm{ + ctx: ctx, + clientConfig: clientConfig, + serverConfig: serverConfig, + serverPort: serverPort, + messageRecvCount: &messageRecvCount, + clientMutex: &sync.Mutex{}, + serverMutex: &sync.Mutex{}, + serverReady: make(chan struct{}), + errChan: make(chan error), + clientChan: make(chan string), + serverChan: make(chan string), + server: server, + client: client, + } + return c +} + +func (c *comm) assert(t *testing.T) { + // DTLS Client + go c.client(c) + + // DTLS Server + go c.server(c) + + defer func() { + if c.clientConn != nil { + if err := c.clientConn.Close(); err != nil { + t.Fatal(err) + } + } + if c.serverConn != nil { + if err := c.serverConn.Close(); err != nil { + t.Fatal(err) + } + } + if c.serverListener != nil { + if err := c.serverListener.Close(); err != nil { + t.Fatal(err) + } + } + }() + + func() { + seenClient, seenServer := false, false + for { + select { + case err := <-c.errChan: + t.Fatal(err) + case <-time.After(testTimeLimit): + t.Fatalf("Test timeout, seenClient %t seenServer %t", seenClient, seenServer) + case clientMsg := <-c.clientChan: + if clientMsg != testMessage { + t.Fatalf("clientMsg does not equal test message: %s %s", clientMsg, testMessage) + } + + seenClient = true + if seenClient && seenServer { + return + } + case serverMsg := <-c.serverChan: + if serverMsg != testMessage { + t.Fatalf("serverMsg does not equal test message: %s %s", serverMsg, testMessage) + } + + seenServer = true + if seenClient && seenServer { + return + } + } + } + }() +} + +func clientPion(c *comm) { + select { + case <-c.serverReady: + // OK + case <-time.After(time.Second): + c.errChan <- errServerTimeout + } + + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + + var err error + c.clientConn, err = dtls.DialWithContext(c.ctx, "udp", + &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: c.serverPort}, + c.clientConfig, + ) + if err != nil { + c.errChan <- err + return + } + + simpleReadWrite(c.errChan, c.clientChan, c.clientConn, c.messageRecvCount) +} + +func serverPion(c *comm) { + c.serverMutex.Lock() + defer c.serverMutex.Unlock() + + var err error + c.serverListener, err = dtls.Listen("udp", + &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: c.serverPort}, + c.serverConfig, + ) + if err != nil { + c.errChan <- err + return + } + c.serverReady <- struct{}{} + c.serverConn, err = c.serverListener.Accept() + if err != nil { + c.errChan <- err + return + } + + simpleReadWrite(c.errChan, c.serverChan, c.serverConn, c.messageRecvCount) +} + +/* + Simple DTLS Client/Server can communicate + - Assert that you can send messages both ways + - Assert that Close() on both ends work + - Assert that no Goroutines are leaked +*/ +func testPionE2ESimple(t *testing.T, server, client func(*comm)) { + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + for _, cipherSuite := range []dtls.CipherSuiteID{ + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + } { + cipherSuite := cipherSuite + t.Run(cipherSuite.String(), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + cert, err := selfsign.GenerateSelfSignedWithDNS("localhost") + if err != nil { + t.Fatal(err) + } + + cfg := &dtls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: []dtls.CipherSuiteID{cipherSuite}, + InsecureSkipVerify: true, + } + serverPort := randomPort(t) + comm := newComm(ctx, cfg, cfg, serverPort, server, client) + comm.assert(t) + }) + } +} + +func testPionE2ESimplePSK(t *testing.T, server, client func(*comm)) { + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + for _, cipherSuite := range []dtls.CipherSuiteID{ + dtls.TLS_PSK_WITH_AES_128_CCM, + dtls.TLS_PSK_WITH_AES_128_CCM_8, + dtls.TLS_PSK_WITH_AES_128_GCM_SHA256, + } { + cipherSuite := cipherSuite + t.Run(cipherSuite.String(), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + cfg := &dtls.Config{ + PSK: func(hint []byte) ([]byte, error) { + return []byte{0xAB, 0xC1, 0x23}, nil + }, + PSKIdentityHint: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + CipherSuites: []dtls.CipherSuiteID{cipherSuite}, + } + serverPort := randomPort(t) + comm := newComm(ctx, cfg, cfg, serverPort, server, client) + comm.assert(t) + }) + } +} + +func testPionE2EMTUs(t *testing.T, server, client func(*comm)) { + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + for _, mtu := range []int{ + 10000, + 1000, + 100, + } { + mtu := mtu + t.Run(fmt.Sprintf("MTU%d", mtu), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cert, err := selfsign.GenerateSelfSignedWithDNS("localhost") + if err != nil { + t.Fatal(err) + } + + cfg := &dtls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + InsecureSkipVerify: true, + MTU: mtu, + } + serverPort := randomPort(t) + comm := newComm(ctx, cfg, cfg, serverPort, server, client) + comm.assert(t) + }) + } +} + +func TestPionE2ESimple(t *testing.T) { + testPionE2ESimple(t, serverPion, clientPion) +} + +func TestPionE2ESimplePSK(t *testing.T) { + testPionE2ESimplePSK(t, serverPion, clientPion) +} + +func TestPionE2EMTUs(t *testing.T) { + testPionE2EMTUs(t, serverPion, clientPion) +} diff --git a/dtls-2.0.9/e2e/e2e_v113_test.go b/dtls-2.0.9/e2e/e2e_v113_test.go new file mode 100644 index 0000000..5d7243f --- /dev/null +++ b/dtls-2.0.9/e2e/e2e_v113_test.go @@ -0,0 +1,62 @@ +// +build go1.13,!js + +package e2e + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "crypto/tls" + "testing" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/transport/test" +) + +// ED25519 is not supported in Go 1.12 crypto/x509. +// Once Go 1.12 is deprecated, move this test to e2e_test.go. + +func testPionE2ESimpleED25519(t *testing.T, server, client func(*comm)) { + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + for _, cipherSuite := range []dtls.CipherSuiteID{ + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + } { + cipherSuite := cipherSuite + t.Run(cipherSuite.String(), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, key, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + cert, err := selfsign.SelfSign(key) + if err != nil { + t.Fatal(err) + } + + cfg := &dtls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: []dtls.CipherSuiteID{cipherSuite}, + InsecureSkipVerify: true, + } + serverPort := randomPort(t) + comm := newComm(ctx, cfg, cfg, serverPort, server, client) + comm.assert(t) + }) + } +} + +func TestPionE2ESimpleED25519(t *testing.T) { + testPionE2ESimpleED25519(t, serverPion, clientPion) +} diff --git a/dtls-2.0.9/errors.go b/dtls-2.0.9/errors.go new file mode 100644 index 0000000..2e16388 --- /dev/null +++ b/dtls-2.0.9/errors.go @@ -0,0 +1,141 @@ +package dtls + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "golang.org/x/xerrors" +) + +// Typed errors +var ( + ErrConnClosed = &FatalError{Err: errors.New("conn is closed")} //nolint:goerr113 + + errDeadlineExceeded = &TimeoutError{Err: xerrors.Errorf("read/write timeout: %w", context.DeadlineExceeded)} + errInvalidContentType = &TemporaryError{Err: errors.New("invalid content type")} //nolint:goerr113 + + errBufferTooSmall = &TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errContextUnsupported = &TemporaryError{Err: errors.New("context is not supported for ExportKeyingMaterial")} //nolint:goerr113 + errHandshakeInProgress = &TemporaryError{Err: errors.New("handshake is in progress")} //nolint:goerr113 + errReservedExportKeyingMaterial = &TemporaryError{Err: errors.New("ExportKeyingMaterial can not be used with a reserved label")} //nolint:goerr113 + errApplicationDataEpochZero = &TemporaryError{Err: errors.New("ApplicationData with epoch of 0")} //nolint:goerr113 + errUnhandledContextType = &TemporaryError{Err: errors.New("unhandled contentType")} //nolint:goerr113 + + errCertificateVerifyNoCertificate = &FatalError{Err: errors.New("client sent certificate verify but we have no certificate to verify")} //nolint:goerr113 + errCipherSuiteNoIntersection = &FatalError{Err: errors.New("client+server do not support any shared cipher suites")} //nolint:goerr113 + errClientCertificateNotVerified = &FatalError{Err: errors.New("client sent certificate but did not verify it")} //nolint:goerr113 + errClientCertificateRequired = &FatalError{Err: errors.New("server required client verification, but got none")} //nolint:goerr113 + errClientNoMatchingSRTPProfile = &FatalError{Err: errors.New("server responded with SRTP Profile we do not support")} //nolint:goerr113 + errClientRequiredButNoServerEMS = &FatalError{Err: errors.New("client required Extended Master Secret extension, but server does not support it")} //nolint:goerr113 + errCookieMismatch = &FatalError{Err: errors.New("client+server cookie does not match")} //nolint:goerr113 + errIdentityNoPSK = &FatalError{Err: errors.New("PSK Identity Hint provided but PSK is nil")} //nolint:goerr113 + errInvalidCertificate = &FatalError{Err: errors.New("no certificate provided")} //nolint:goerr113 + errInvalidCipherSuite = &FatalError{Err: errors.New("invalid or unknown cipher suite")} //nolint:goerr113 + errInvalidECDSASignature = &FatalError{Err: errors.New("ECDSA signature contained zero or negative values")} //nolint:goerr113 + errInvalidPrivateKey = &FatalError{Err: errors.New("invalid private key type")} //nolint:goerr113 + errInvalidSignatureAlgorithm = &FatalError{Err: errors.New("invalid signature algorithm")} //nolint:goerr113 + errKeySignatureMismatch = &FatalError{Err: errors.New("expected and actual key signature do not match")} //nolint:goerr113 + errNilNextConn = &FatalError{Err: errors.New("Conn can not be created with a nil nextConn")} //nolint:goerr113 + errNoAvailableCipherSuites = &FatalError{Err: errors.New("connection can not be created, no CipherSuites satisfy this Config")} //nolint:goerr113 + errNoAvailablePSKCipherSuite = &FatalError{Err: errors.New("connection can not be created, pre-shared key present but no compatible CipherSuite")} //nolint:goerr113 + errNoAvailableCertificateCipherSuite = &FatalError{Err: errors.New("connection can not be created, certificate present but no compatible CipherSuite")} //nolint:goerr113 + errNoAvailableSignatureSchemes = &FatalError{Err: errors.New("connection can not be created, no SignatureScheme satisfy this Config")} //nolint:goerr113 + errNoCertificates = &FatalError{Err: errors.New("no certificates configured")} //nolint:goerr113 + errNoConfigProvided = &FatalError{Err: errors.New("no config provided")} //nolint:goerr113 + errNoSupportedEllipticCurves = &FatalError{Err: errors.New("client requested zero or more elliptic curves that are not supported by the server")} //nolint:goerr113 + errUnsupportedProtocolVersion = &FatalError{Err: errors.New("unsupported protocol version")} //nolint:goerr113 + errPSKAndIdentityMustBeSetForClient = &FatalError{Err: errors.New("PSK and PSK Identity Hint must both be set for client")} //nolint:goerr113 + errRequestedButNoSRTPExtension = &FatalError{Err: errors.New("SRTP support was requested but server did not respond with use_srtp extension")} //nolint:goerr113 + errServerNoMatchingSRTPProfile = &FatalError{Err: errors.New("client requested SRTP but we have no matching profiles")} //nolint:goerr113 + errServerRequiredButNoClientEMS = &FatalError{Err: errors.New("server requires the Extended Master Secret extension, but the client does not support it")} //nolint:goerr113 + errVerifyDataMismatch = &FatalError{Err: errors.New("expected and actual verify data does not match")} //nolint:goerr113 + + errInvalidFlight = &InternalError{Err: errors.New("invalid flight number")} //nolint:goerr113 + errKeySignatureGenerateUnimplemented = &InternalError{Err: errors.New("unable to generate key signature, unimplemented")} //nolint:goerr113 + errKeySignatureVerifyUnimplemented = &InternalError{Err: errors.New("unable to verify key signature, unimplemented")} //nolint:goerr113 + errLengthMismatch = &InternalError{Err: errors.New("data length and declared length do not match")} //nolint:goerr113 + errSequenceNumberOverflow = &InternalError{Err: errors.New("sequence number overflow")} //nolint:goerr113 + errInvalidFSMTransition = &InternalError{Err: errors.New("invalid state machine transition")} //nolint:goerr113 +) + +// 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 + +// invalidCipherSuite indicates an attempt at using an unsupported cipher suite. +type invalidCipherSuite struct { + id CipherSuiteID +} + +func (e *invalidCipherSuite) Error() string { + return fmt.Sprintf("CipherSuite with id(%d) is not valid", e.id) +} + +func (e *invalidCipherSuite) Is(err error) bool { + if other, ok := err.(*invalidCipherSuite); ok { + return e.id == other.id + } + return false +} + +// errAlert wraps DTLS alert notification as an error +type errAlert struct { + *alert.Alert +} + +func (e *errAlert) Error() string { + return fmt.Sprintf("alert: %s", e.Alert.String()) +} + +func (e *errAlert) IsFatalOrCloseNotify() bool { + return e.Level == alert.Fatal || e.Description == alert.CloseNotify +} + +func (e *errAlert) Is(err error) bool { + if other, ok := err.(*errAlert); ok { + 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 err { + case io.EOF, context.Canceled, context.DeadlineExceeded: + // Return io.EOF and context errors as is. + return err + } + switch e := err.(type) { + case (*net.OpError): + if se, ok := e.Err.(*os.SyscallError); ok { + if se.Timeout() { + return &TimeoutError{Err: err} + } + if isOpErrorTemporary(se) { + return &TemporaryError{Err: err} + } + } + case (net.Error): + return err + } + return &FatalError{Err: err} +} diff --git a/dtls-2.0.9/errors_errno.go b/dtls-2.0.9/errors_errno.go new file mode 100644 index 0000000..a9a439b --- /dev/null +++ b/dtls-2.0.9/errors_errno.go @@ -0,0 +1,25 @@ +// +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 ( + "os" + "syscall" +) + +func isOpErrorTemporary(err *os.SyscallError) bool { + if ne, ok := err.Err.(syscall.Errno); ok { + switch ne { + case syscall.ECONNREFUSED: + return true + default: + return false + } + } + return false +} diff --git a/dtls-2.0.9/errors_errno_test.go b/dtls-2.0.9/errors_errno_test.go new file mode 100644 index 0000000..7c567ee --- /dev/null +++ b/dtls-2.0.9/errors_errno_test.go @@ -0,0 +1,41 @@ +// +build aix darwin dragonfly freebsd linux nacl nacljs netbsd openbsd solaris windows + +// For systems having syscall.Errno. +// The build target must be same as errors_errno.go. + +package dtls + +import ( + "net" + "testing" +) + +func TestErrorsTemporary(t *testing.T) { + addrListen, errListen := net.ResolveUDPAddr("udp", "localhost:0") + if errListen != nil { + t.Fatalf("Unexpected error: %v", errListen) + } + // Server is not listening. + conn, errDial := net.DialUDP("udp", nil, addrListen) + if errDial != nil { + t.Fatalf("Unexpected error: %v", errDial) + } + + _, _ = conn.Write([]byte{0x00}) // trigger + _, err := conn.Read(make([]byte, 10)) + _ = conn.Close() + + if err == nil { + t.Skip("ECONNREFUSED is not set by system") + } + ne, ok := netError(err).(net.Error) + if !ok { + t.Fatalf("netError must return net.Error") + } + if ne.Timeout() { + t.Errorf("%v must not be timeout error", err) + } + if !ne.Temporary() { + t.Errorf("%v must be temporary error", err) + } +} diff --git a/dtls-2.0.9/errors_noerrno.go b/dtls-2.0.9/errors_noerrno.go new file mode 100644 index 0000000..fcc37ce --- /dev/null +++ b/dtls-2.0.9/errors_noerrno.go @@ -0,0 +1,14 @@ +// +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 +} diff --git a/dtls-2.0.9/errors_test.go b/dtls-2.0.9/errors_test.go new file mode 100644 index 0000000..0234315 --- /dev/null +++ b/dtls-2.0.9/errors_test.go @@ -0,0 +1,85 @@ +package dtls + +import ( + "errors" + "fmt" + "net" + "testing" + + "golang.org/x/xerrors" +) + +var errExample = errors.New("an example error") + +func TestErrorUnwrap(t *testing.T) { + cases := []struct { + err error + errUnwrapped []error + }{ + { + &FatalError{Err: errExample}, + []error{errExample}, + }, + { + &TemporaryError{Err: errExample}, + []error{errExample}, + }, + { + &InternalError{Err: errExample}, + []error{errExample}, + }, + { + &TimeoutError{Err: errExample}, + []error{errExample}, + }, + { + &HandshakeError{Err: errExample}, + []error{errExample}, + }, + } + for _, c := range cases { + c := c + t.Run(fmt.Sprintf("%T", c.err), func(t *testing.T) { + err := c.err + for _, unwrapped := range c.errUnwrapped { + e := xerrors.Unwrap(err) + if !errors.Is(e, unwrapped) { + t.Errorf("Unwrapped error is expected to be '%v', got '%v'", unwrapped, e) + } + } + }) + } +} + +func TestErrorNetError(t *testing.T) { + cases := []struct { + err error + str string + timeout, temporary bool + }{ + {&FatalError{Err: errExample}, "dtls fatal: an example error", false, false}, + {&TemporaryError{Err: errExample}, "dtls temporary: an example error", false, true}, + {&InternalError{Err: errExample}, "dtls internal: an example error", false, false}, + {&TimeoutError{Err: errExample}, "dtls timeout: an example error", true, true}, + {&HandshakeError{Err: errExample}, "handshake error: an example error", false, false}, + {&HandshakeError{Err: &TimeoutError{Err: errExample}}, "handshake error: dtls timeout: an example error", true, true}, + } + for _, c := range cases { + c := c + t.Run(fmt.Sprintf("%T", c.err), func(t *testing.T) { + ne, ok := c.err.(net.Error) + if !ok { + t.Fatalf("%T doesn't implement net.Error", c.err) + } + if ne.Timeout() != c.timeout { + t.Errorf("%T.Timeout() should be %v", c.err, c.timeout) + } + if ne.Temporary() != c.temporary { + t.Errorf("%T.Temporary() should be %v", c.err, c.temporary) + } + if ne.Error() != c.str { + t.Errorf("%T.Error() should be %v", c.err, c.str) + } + }) + } +} diff --git a/dtls-2.0.9/examples/certificates/README.md b/dtls-2.0.9/examples/certificates/README.md new file mode 100644 index 0000000..aef3d09 --- /dev/null +++ b/dtls-2.0.9/examples/certificates/README.md @@ -0,0 +1,26 @@ +# Certificates + +The certificates in for the examples are generated using the commands shown below. + +Note that this was run on OpenSSL 1.1.1d, of which the arguments can be found in the [OpenSSL Manpages](https://www.openssl.org/docs/man1.1.1/man1), and is not guaranteed to work on different OpenSSL versions. + +```shell +# Extensions required for certificate validation. +$ EXTFILE='extfile.conf' +$ echo 'subjectAltName = IP:127.0.0.1\nbasicConstraints = critical,CA:true' > "${EXTFILE}" + +# Server. +$ SERVER_NAME='server' +$ openssl ecparam -name prime256v1 -genkey -noout -out "${SERVER_NAME}.pem" +$ openssl req -key "${SERVER_NAME}.pem" -new -sha256 -subj '/C=NL' -out "${SERVER_NAME}.csr" +$ openssl x509 -req -in "${SERVER_NAME}.csr" -extfile "${EXTFILE}" -days 365 -signkey "${SERVER_NAME}.pem" -sha256 -out "${SERVER_NAME}.pub.pem" + +# Client. +$ CLIENT_NAME='client' +$ openssl ecparam -name prime256v1 -genkey -noout -out "${CLIENT_NAME}.pem" +$ openssl req -key "${CLIENT_NAME}.pem" -new -sha256 -subj '/C=NL' -out "${CLIENT_NAME}.csr" +$ openssl x509 -req -in "${CLIENT_NAME}.csr" -extfile "${EXTFILE}" -days 365 -CA "${SERVER_NAME}.pub.pem" -CAkey "${SERVER_NAME}.pem" -set_serial '0xabcd' -sha256 -out "${CLIENT_NAME}.pub.pem" + +# Cleanup. +$ rm "${EXTFILE}" "${SERVER_NAME}.csr" "${CLIENT_NAME}.csr" +``` diff --git a/dtls-2.0.9/examples/certificates/client.pem b/dtls-2.0.9/examples/certificates/client.pem new file mode 100644 index 0000000..f092d50 --- /dev/null +++ b/dtls-2.0.9/examples/certificates/client.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGOO78dEAcepxdUIeDzC28jMcFrJr2q7x+UdhgtJ/RS3oAoGCCqGSM49 +AwEHoUQDQgAEGLSNxlkJ9mETKI2Hogq3Cyh06pJKA1YMgcKqYKS6yQQlvvk5rU88 ++RojFPgXJukymhfIJmw4eGxxEMSjuEZY7w== +-----END EC PRIVATE KEY----- diff --git a/dtls-2.0.9/examples/certificates/client.pub.pem b/dtls-2.0.9/examples/certificates/client.pub.pem new file mode 100644 index 0000000..1259953 --- /dev/null +++ b/dtls-2.0.9/examples/certificates/client.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBLTCB1aADAgECAgMAq80wCgYIKoZIzj0EAwIwDTELMAkGA1UEBhMCTkwwHhcN +MjAwMzIwMDk0NjQ0WhcNMjEwMzIwMDk0NjQ0WjANMQswCQYDVQQGEwJOTDBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABBi0jcZZCfZhEyiNh6IKtwsodOqSSgNWDIHC +qmCkuskEJb75Oa1PPPkaIxT4FybpMpoXyCZsOHhscRDEo7hGWO+jJDAiMA8GA1Ud +EQQIMAaHBH8AAAEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBx +sIkcADN9E60veZOFOeANaRWAiQaLWZfUxqkOmfHztQIgI2CfHMjDQwJZFh35HvFs +NOPJj8wxFhqR5pqMF23cgOY= +-----END CERTIFICATE----- diff --git a/dtls-2.0.9/examples/certificates/server.pem b/dtls-2.0.9/examples/certificates/server.pem new file mode 100644 index 0000000..5a559d8 --- /dev/null +++ b/dtls-2.0.9/examples/certificates/server.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDT8Xyx5RpPP+98ulYZKsvKIVdBUJug/L9H2M8JThv+GoAoGCCqGSM49 +AwEHoUQDQgAE6Wf0qQqIb5G7g51P83Dh1Yst52kyntGYz1Bt6S7crpmQFs9ZRZMy +bJ6MGIwGcVBMgoL3pfxDKdZ3mnzmoibU0w== +-----END EC PRIVATE KEY----- diff --git a/dtls-2.0.9/examples/certificates/server.pub.pem b/dtls-2.0.9/examples/certificates/server.pub.pem new file mode 100644 index 0000000..e1cf479 --- /dev/null +++ b/dtls-2.0.9/examples/certificates/server.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBPzCB5qADAgECAhRtzyVTL+9D0KHfbcKYeKckpLVRmTAKBggqhkjOPQQDAjAN +MQswCQYDVQQGEwJOTDAeFw0yMDAzMjAwOTQ2NDRaFw0yMTAzMjAwOTQ2NDRaMA0x +CzAJBgNVBAYTAk5MMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Wf0qQqIb5G7 +g51P83Dh1Yst52kyntGYz1Bt6S7crpmQFs9ZRZMybJ6MGIwGcVBMgoL3pfxDKdZ3 +mnzmoibU06MkMCIwDwYDVR0RBAgwBocEfwAAATAPBgNVHRMBAf8EBTADAQH/MAoG +CCqGSM49BAMCA0gAMEUCIQD000SU+klkNLGvHZcMYNVkCFsImnGKIqPMy3LELSiF +0gIgSGIFkNEIAyNxn44CXZJu3piyz1ouK2fLefDJMYfcXgM= +-----END CERTIFICATE----- diff --git a/dtls-2.0.9/examples/dial/psk/main.go b/dtls-2.0.9/examples/dial/psk/main.go new file mode 100644 index 0000000..dfac162 --- /dev/null +++ b/dtls-2.0.9/examples/dial/psk/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + PSK: func(hint []byte) ([]byte, error) { + fmt.Printf("Server's hint: %s \n", hint) + return []byte{0xAB, 0xC1, 0x23}, nil + }, + PSKIdentityHint: []byte("Pion DTLS Server"), + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_PSK_WITH_AES_128_CCM_8}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + } + + // Connect to a DTLS server + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + dtlsConn, err := dtls.DialWithContext(ctx, "udp", addr, config) + util.Check(err) + defer func() { + util.Check(dtlsConn.Close()) + }() + + fmt.Println("Connected; type 'exit' to shutdown gracefully") + + // Simulate a chat session + util.Chat(dtlsConn) +} diff --git a/dtls-2.0.9/examples/dial/selfsign/main.go b/dtls-2.0.9/examples/dial/selfsign/main.go new file mode 100644 index 0000000..7ff2926 --- /dev/null +++ b/dtls-2.0.9/examples/dial/selfsign/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // Generate a certificate and private key to secure the connection + certificate, genErr := selfsign.GenerateSelfSigned() + util.Check(genErr) + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + Certificates: []tls.Certificate{certificate}, + InsecureSkipVerify: true, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + } + + // Connect to a DTLS server + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + dtlsConn, err := dtls.DialWithContext(ctx, "udp", addr, config) + util.Check(err) + defer func() { + util.Check(dtlsConn.Close()) + }() + + fmt.Println("Connected; type 'exit' to shutdown gracefully") + + // Simulate a chat session + util.Chat(dtlsConn) +} diff --git a/dtls-2.0.9/examples/dial/verify/main.go b/dtls-2.0.9/examples/dial/verify/main.go new file mode 100644 index 0000000..53340da --- /dev/null +++ b/dtls-2.0.9/examples/dial/verify/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + certificate, err := util.LoadKeyAndCertificate("examples/certificates/client.pem", + "examples/certificates/client.pub.pem") + util.Check(err) + + rootCertificate, err := util.LoadCertificate("examples/certificates/server.pub.pem") + util.Check(err) + certPool := x509.NewCertPool() + cert, err := x509.ParseCertificate(rootCertificate.Certificate[0]) + util.Check(err) + certPool.AddCert(cert) + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + Certificates: []tls.Certificate{*certificate}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + RootCAs: certPool, + } + + // Connect to a DTLS server + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + dtlsConn, err := dtls.DialWithContext(ctx, "udp", addr, config) + util.Check(err) + defer func() { + util.Check(dtlsConn.Close()) + }() + + fmt.Println("Connected; type 'exit' to shutdown gracefully") + + // Simulate a chat session + util.Chat(dtlsConn) +} diff --git a/dtls-2.0.9/examples/listen/psk/main.go b/dtls-2.0.9/examples/listen/psk/main.go new file mode 100644 index 0000000..72a6c23 --- /dev/null +++ b/dtls-2.0.9/examples/listen/psk/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // Create parent context to cleanup handshaking connections on exit. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + PSK: func(hint []byte) ([]byte, error) { + fmt.Printf("Client's hint: %s \n", hint) + return []byte{0xAB, 0xC1, 0x23}, nil + }, + PSKIdentityHint: []byte("Pion DTLS Client"), + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_PSK_WITH_AES_128_CCM_8}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + // Create timeout context for accepted connection. + ConnectContextMaker: func() (context.Context, func()) { + return context.WithTimeout(ctx, 30*time.Second) + }, + } + + // Connect to a DTLS server + listener, err := dtls.Listen("udp", addr, config) + util.Check(err) + defer func() { + util.Check(listener.Close()) + }() + + fmt.Println("Listening") + + // Simulate a chat session + hub := util.NewHub() + + go func() { + for { + // Wait for a connection. + conn, err := listener.Accept() + util.Check(err) + // defer conn.Close() // TODO: graceful shutdown + + // `conn` is of type `net.Conn` but may be casted to `dtls.Conn` + // using `dtlsConn := conn.(*dtls.Conn)` in order to to expose + // functions like `ConnectionState` etc. + + // Register the connection with the chat hub + if err == nil { + hub.Register(conn) + } + } + }() + + // Start chatting + hub.Chat() +} diff --git a/dtls-2.0.9/examples/listen/selfsign/main.go b/dtls-2.0.9/examples/listen/selfsign/main.go new file mode 100644 index 0000000..2df4ac4 --- /dev/null +++ b/dtls-2.0.9/examples/listen/selfsign/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // Generate a certificate and private key to secure the connection + certificate, genErr := selfsign.GenerateSelfSigned() + util.Check(genErr) + + // Create parent context to cleanup handshaking connections on exit. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + Certificates: []tls.Certificate{certificate}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + // Create timeout context for accepted connection. + ConnectContextMaker: func() (context.Context, func()) { + return context.WithTimeout(ctx, 30*time.Second) + }, + } + + // Connect to a DTLS server + listener, err := dtls.Listen("udp", addr, config) + util.Check(err) + defer func() { + util.Check(listener.Close()) + }() + + fmt.Println("Listening") + + // Simulate a chat session + hub := util.NewHub() + + go func() { + for { + // Wait for a connection. + conn, err := listener.Accept() + util.Check(err) + // defer conn.Close() // TODO: graceful shutdown + + // `conn` is of type `net.Conn` but may be casted to `dtls.Conn` + // using `dtlsConn := conn.(*dtls.Conn)` in order to to expose + // functions like `ConnectionState` etc. + + // Register the connection with the chat hub + if err == nil { + hub.Register(conn) + } + } + }() + + // Start chatting + hub.Chat() +} diff --git a/dtls-2.0.9/examples/listen/verify/main.go b/dtls-2.0.9/examples/listen/verify/main.go new file mode 100644 index 0000000..96f5c0d --- /dev/null +++ b/dtls-2.0.9/examples/listen/verify/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/examples/util" +) + +func main() { + // Prepare the IP to connect to + addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444} + + // Create parent context to cleanup handshaking connections on exit. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // + // Everything below is the pion-DTLS API! Thanks for using it ❤️. + // + + certificate, err := util.LoadKeyAndCertificate("examples/certificates/server.pem", + "examples/certificates/server.pub.pem") + util.Check(err) + + rootCertificate, err := util.LoadCertificate("examples/certificates/server.pub.pem") + util.Check(err) + certPool := x509.NewCertPool() + cert, err := x509.ParseCertificate(rootCertificate.Certificate[0]) + util.Check(err) + certPool.AddCert(cert) + + // Prepare the configuration of the DTLS connection + config := &dtls.Config{ + Certificates: []tls.Certificate{*certificate}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + ClientAuth: dtls.RequireAndVerifyClientCert, + ClientCAs: certPool, + // Create timeout context for accepted connection. + ConnectContextMaker: func() (context.Context, func()) { + return context.WithTimeout(ctx, 30*time.Second) + }, + } + + // Connect to a DTLS server + listener, err := dtls.Listen("udp", addr, config) + util.Check(err) + defer func() { + util.Check(listener.Close()) + }() + + fmt.Println("Listening") + + // Simulate a chat session + hub := util.NewHub() + + go func() { + for { + // Wait for a connection. + conn, err := listener.Accept() + util.Check(err) + // defer conn.Close() // TODO: graceful shutdown + + // `conn` is of type `net.Conn` but may be casted to `dtls.Conn` + // using `dtlsConn := conn.(*dtls.Conn)` in order to to expose + // functions like `ConnectionState` etc. + + // Register the connection with the chat hub + hub.Register(conn) + } + }() + + // Start chatting + hub.Chat() +} diff --git a/dtls-2.0.9/examples/util/hub.go b/dtls-2.0.9/examples/util/hub.go new file mode 100644 index 0000000..ad8e597 --- /dev/null +++ b/dtls-2.0.9/examples/util/hub.go @@ -0,0 +1,80 @@ +package util + +import ( + "bufio" + "fmt" + "net" + "os" + "strings" + "sync" +) + +// Hub is a helper to handle one to many chat +type Hub struct { + conns map[string]net.Conn + lock sync.RWMutex +} + +// NewHub builds a new hub +func NewHub() *Hub { + return &Hub{conns: make(map[string]net.Conn)} +} + +// Register adds a new conn to the Hub +func (h *Hub) Register(conn net.Conn) { + fmt.Printf("Connected to %s\n", conn.RemoteAddr()) + h.lock.Lock() + defer h.lock.Unlock() + + h.conns[conn.RemoteAddr().String()] = conn + + go h.readLoop(conn) +} + +func (h *Hub) readLoop(conn net.Conn) { + b := make([]byte, bufSize) + for { + n, err := conn.Read(b) + if err != nil { + h.unregister(conn) + return + } + fmt.Printf("Got message: %s\n", string(b[:n])) + } +} + +func (h *Hub) unregister(conn net.Conn) { + h.lock.Lock() + defer h.lock.Unlock() + delete(h.conns, conn.RemoteAddr().String()) + err := conn.Close() + if err != nil { + fmt.Println("Failed to disconnect", conn.RemoteAddr(), err) + } else { + fmt.Println("Disconnected ", conn.RemoteAddr()) + } +} + +func (h *Hub) broadcast(msg []byte) { + h.lock.RLock() + defer h.lock.RUnlock() + for _, conn := range h.conns { + _, err := conn.Write(msg) + if err != nil { + fmt.Printf("Failed to write message to %s: %v\n", conn.RemoteAddr(), err) + } + } +} + +// Chat starts the stdin readloop to dispatch messages to the hub +func (h *Hub) Chat() { + reader := bufio.NewReader(os.Stdin) + for { + msg, err := reader.ReadString('\n') + Check(err) + if strings.TrimSpace(msg) == "exit" { + return + } + h.broadcast([]byte(msg)) + } +} diff --git a/dtls-2.0.9/examples/util/util.go b/dtls-2.0.9/examples/util/util.go new file mode 100644 index 0000000..8f53539 --- /dev/null +++ b/dtls-2.0.9/examples/util/util.go @@ -0,0 +1,154 @@ +// Package util provides auxiliary utilities used in examples +package util + +import ( + "bufio" + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" +) + +const bufSize = 8192 + +var ( + errBlockIsNotPrivateKey = errors.New("block is not a private key, unable to load key") + errUnknownKeyTime = errors.New("unknown key time in PKCS#8 wrapping, unable to load key") + errNoPrivateKeyFound = errors.New("no private key found, unable to load key") + errBlockIsNotCertificate = errors.New("block is not a certificate, unable to load certificates") + errNoCertificateFound = errors.New("no certificate found, unable to load certificates") +) + +// Chat simulates a simple text chat session over the connection +func Chat(conn io.ReadWriter) { + go func() { + b := make([]byte, bufSize) + + for { + n, err := conn.Read(b) + Check(err) + fmt.Printf("Got message: %s\n", string(b[:n])) + } + }() + + reader := bufio.NewReader(os.Stdin) + + for { + text, err := reader.ReadString('\n') + Check(err) + + if strings.TrimSpace(text) == "exit" { + return + } + + _, err = conn.Write([]byte(text)) + Check(err) + } +} + +// Check is a helper to throw errors in the examples +func Check(err error) { + switch e := err.(type) { + case nil: + case (net.Error): + if e.Temporary() { + fmt.Printf("Warning: %v\n", err) + return + } + + fmt.Printf("net.Error: %v\n", err) + panic(err) + default: + fmt.Printf("error: %v\n", err) + panic(err) + } +} + +// LoadKeyAndCertificate reads certificates or key from file +func LoadKeyAndCertificate(keyPath string, certificatePath string) (*tls.Certificate, error) { + privateKey, err := LoadKey(keyPath) + if err != nil { + return nil, err + } + + certificate, err := LoadCertificate(certificatePath) + if err != nil { + return nil, err + } + + certificate.PrivateKey = privateKey + + return certificate, nil +} + +// LoadKey Load/read key from file +func LoadKey(path string) (crypto.PrivateKey, error) { + rawData, err := ioutil.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(rawData) + if block == nil || !strings.HasSuffix(block.Type, "PRIVATE KEY") { + return nil, errBlockIsNotPrivateKey + } + + if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + return key, nil + } + + if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey: + return key, nil + default: + return nil, errUnknownKeyTime + } + } + + if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + return key, nil + } + + return nil, errNoPrivateKeyFound +} + +// LoadCertificate Load/read certificate(s) from file +func LoadCertificate(path string) (*tls.Certificate, error) { + rawData, err := ioutil.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, err + } + + var certificate tls.Certificate + + for { + block, rest := pem.Decode(rawData) + if block == nil { + break + } + + if block.Type != "CERTIFICATE" { + return nil, errBlockIsNotCertificate + } + + certificate.Certificate = append(certificate.Certificate, block.Bytes) + rawData = rest + } + + if len(certificate.Certificate) == 0 { + return nil, errNoCertificateFound + } + + return &certificate, nil +} diff --git a/dtls-2.0.9/flight.go b/dtls-2.0.9/flight.go new file mode 100644 index 0000000..580ee48 --- /dev/null +++ b/dtls-2.0.9/flight.go @@ -0,0 +1,75 @@ +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 + 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 / + +*/ + +type flightVal uint8 + +const ( + flight0 flightVal = iota + 1 + flight1 + flight2 + flight3 + flight4 + flight5 + flight6 +) + +func (f flightVal) String() string { + 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 flight5: + return "Flight 5" + case flight6: + return "Flight 6" + default: + return "Invalid Flight" + } +} + +func (f flightVal) isLastSendFlight() bool { + return f == flight6 +} + +func (f flightVal) isLastRecvFlight() bool { + return f == flight5 +} diff --git a/dtls-2.0.9/flight0handler.go b/dtls-2.0.9/flight0handler.go new file mode 100644 index 0000000..949d7c0 --- /dev/null +++ b/dtls-2.0.9/flight0handler.go @@ -0,0 +1,102 @@ +package dtls + +import ( + "context" + "crypto/rand" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" +) + +func flight0Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + seq, msgs, ok := cache.fullPullMap(0, + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, 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 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 e := val.(type) { + case *extension.SupportedEllipticCurves: + if len(e.EllipticCurves) == 0 { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoSupportedEllipticCurves + } + state.namedCurve = e.EllipticCurves[0] + case *extension.UseSRTP: + profile, ok := findMatchingSRTPProfile(e.ProtectionProfiles, cfg.localSRTPProtectionProfiles) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errServerNoMatchingSRTPProfile + } + state.srtpProtectionProfile = profile + case *extension.UseExtendedMasterSecret: + if cfg.extendedMasterSecret != DisableExtendedMasterSecret { + state.extendedMasterSecret = true + } + case *extension.ServerName: + state.serverName = e.ServerName // remote server name + } + } + + 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 + } + } + + return flight2, nil, nil +} + +func flight0Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + // Initialize + 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 +} diff --git a/dtls-2.0.9/flight1handler.go b/dtls-2.0.9/flight1handler.go new file mode 100644 index 0000000..9229292 --- /dev/null +++ b/dtls-2.0.9/flight1handler.go @@ -0,0 +1,112 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight1Parse(ctx context.Context, c 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, + 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, c, 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 +} + +func flight1Generate(c flightConn, state *State, cache *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 + } + + extensions := []extension.Extension{ + &extension.SupportedSignatureAlgorithms{ + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + &extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }, + } + if cfg.localPSKCallback == nil { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &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}) + } + + return []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: state.cookie, + Random: state.localRandom, + CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + }, + }, + }, nil, nil +} diff --git a/dtls-2.0.9/flight2handler.go b/dtls-2.0.9/flight2handler.go new file mode 100644 index 0000000..33e2ee7 --- /dev/null +++ b/dtls-2.0.9/flight2handler.go @@ -0,0 +1,78 @@ +package dtls + +import ( + "bytes" + "context" + "fmt" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/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, + 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 + } + + // TODO 添加 CiscoCompat 支持 + if cfg.localCiscoCompatCallback != nil { + var err error + state.SessionID = clientHello.SessionID + if len(state.SessionID) == 0 { + err = fmt.Errorf("clientHello SessionID is nil") + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err + } + + state.masterSecret, err = cfg.localCiscoCompatCallback(state.SessionID) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err + } + } + + return flight4, nil, nil +} + +func flight2Generate(c flightConn, state *State, cache *handshakeCache, cfg *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 +} diff --git a/dtls-2.0.9/flight3handler.go b/dtls-2.0.9/flight3handler.go new file mode 100644 index 0000000..f953be8 --- /dev/null +++ b/dtls-2.0.9/flight3handler.go @@ -0,0 +1,194 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight3Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { //nolint:gocognit + // 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, + 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 + } + } + + if cfg.localPSKCallback != nil { + seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, true}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + ) + } else { + seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + 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.TypeServerHello].(*handshake.MessageServerHello); ok { + if !h.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + for _, v := range h.Extensions { + switch e := v.(type) { + case *extension.UseSRTP: + profile, ok := findMatchingSRTPProfile(e.ProtectionProfiles, cfg.localSRTPProtectionProfiles) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, errClientNoMatchingSRTPProfile + } + state.srtpProtectionProfile = profile + case *extension.UseExtendedMasterSecret: + if cfg.extendedMasterSecret != DisableExtendedMasterSecret { + state.extendedMasterSecret = true + } + } + } + if cfg.extendedMasterSecret == RequireExtendedMasterSecret && !state.extendedMasterSecret { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errClientRequiredButNoServerEMS + } + if len(cfg.localSRTPProtectionProfiles) > 0 && state.srtpProtectionProfile == 0 { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errRequestedButNoSRTPExtension + } + + remoteCipherSuite := cipherSuiteForID(CipherSuiteID(*h.CipherSuiteID), cfg.customCipherSuites) + if remoteCipherSuite == nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errCipherSuiteNoIntersection + } + + selectedCipherSuite, ok := findMatchingCipherSuite([]CipherSuite{remoteCipherSuite}, cfg.localCipherSuites) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errInvalidCipherSuite + } + + state.cipherSuite = selectedCipherSuite + state.remoteRandom = h.Random + cfg.log.Tracef("[handshake] use cipher suite: %s", selectedCipherSuite.String()) + } + + 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(c, state, cfg, h) + if err != nil { + return 0, alertPtr, err + } + } + + if _, ok := msgs[handshake.TypeCertificateRequest].(*handshake.MessageCertificateRequest); ok { + state.remoteRequestedCertificate = true + } + + return flight5, nil, nil +} + +func handleServerKeyExchange(_ flightConn, state *State, cfg *handshakeConfig, h *handshake.MessageServerKeyExchange) (*alert.Alert, error) { + var err error + if cfg.localPSKCallback != nil { + var psk []byte + if psk, err = cfg.localPSKCallback(h.IdentityHint); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.IdentityHint = h.IdentityHint + state.preMasterSecret = prf.PSKPreMasterSecret(psk) + } else { + if state.localKeypair, err = elliptic.GenerateKeypair(h.NamedCurve); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + if state.preMasterSecret, err = prf.PreMasterSecret(h.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + return nil, nil +} + +func flight3Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + extensions := []extension.Extension{ + &extension.SupportedSignatureAlgorithms{ + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + &extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }, + } + if cfg.localPSKCallback == nil { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &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}) + } + + return []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: state.cookie, + Random: state.localRandom, + CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + }, + }, + }, nil, nil +} diff --git a/dtls-2.0.9/flight4handler.go b/dtls-2.0.9/flight4handler.go new file mode 100644 index 0000000..1464854 --- /dev/null +++ b/dtls-2.0.9/flight4handler.go @@ -0,0 +1,352 @@ +package dtls + +import ( + "context" + "crypto/x509" + + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight4Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { //nolint:gocognit + seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + 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 h, hasCertVerify := msgs[handshake.TypeCertificateVerify].(*handshake.MessageCertificateVerify); hasCertVerify { + 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 == h.HashAlgorithm && ss.Signature == h.SignatureAlgorithm { + validSignatureScheme = true + break + } + } + if !validSignatureScheme { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes + } + + if err := verifyCertificateVerify(plainText, h.HashAlgorithm, h.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 { + if chains, err = verifyClientCert(state.PeerCertificates, cfg.clientCAs); 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 + } + + if !state.cipherSuite.IsInitialized() { + 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 + preMasterSecret = prf.PSKPreMasterSecret(psk) + } 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) + } + + // Now, encrypted packets can be handled + if err := c.handleQueuedPackets(ctx); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + seq, msgs, ok = cache.fullPullMap(seq, + 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 { + 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: + return flight6, nil, nil + } + + return flight6, nil, nil +} + +func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + 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.srtpProtectionProfile != 0 { + extensions = append(extensions, &extension.UseSRTP{ + ProtectionProfiles: []SRTPProtectionProfile{state.srtpProtectionProfile}, + }) + } + if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &extension.SupportedPointFormats{ + PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed}, + }, + }...) + } + + var pkts []*packet + cipherSuiteID := uint16(state.cipherSuite.ID()) + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerHello{ + Version: protocol.Version1_2, + Random: state.localRandom, + SessionID: state.SessionID, + CipherSuiteID: &cipherSuiteID, + CompressionMethod: defaultCompressionMethods()[0], + Extensions: extensions, + }, + }, + }, + }) + + // TODO 添加 CiscoCompat 支持 + if cfg.localCiscoCompatCallback != nil { + if !state.cipherSuite.IsInitialized() { + serverRandom := state.localRandom.MarshalFixed() + clientRandom := state.remoteRandom.MarshalFixed() + + if err := state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], false); err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret) + } + return pkts, nil, nil + } + + switch { + case state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate: + certificate, err := cfg.getCertificate(cfg.serverName) + 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() + + // Find compatible signature scheme + signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, certificate.PrivateKey) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err + } + + signature, err := generateKeySignature(clientRandom[:], serverRandom[:], state.localKeypair.PublicKey, state.namedCurve, certificate.PrivateKey, signatureHashAlgo.Hash) + 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 { + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificateRequest{ + CertificateTypes: []clientcertificate.Type{clientcertificate.RSASign, clientcertificate.ECDSASign}, + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + }, + }, + }) + } + case cfg.localPSKIdentityHint != nil: + // 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, the ServerKeyExchange message is omitted. + // + // https://tools.ietf.org/html/rfc4279#section-2 + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerKeyExchange{ + IdentityHint: cfg.localPSKIdentityHint, + }, + }, + }, + }) + case state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous: + 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, + }, + }, + }, + }) + } + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerHelloDone{}, + }, + }, + }) + + return pkts, nil, nil +} diff --git a/dtls-2.0.9/flight5handler.go b/dtls-2.0.9/flight5handler.go new file mode 100644 index 0000000..baa1d5c --- /dev/null +++ b/dtls-2.0.9/flight5handler.go @@ -0,0 +1,323 @@ +package dtls + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight5Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + _, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + 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 + } + + return flight5, nil, nil +} + +func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { //nolint:gocognit + var certBytes [][]byte + var privateKey crypto.PrivateKey + if len(cfg.localCertificates) > 0 { + certificate, err := cfg.getCertificate(cfg.serverName) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err + } + certBytes = certificate.Certificate + privateKey = certificate.PrivateKey + } + + var pkts []*packet + + if state.remoteRequestedCertificate { + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificate{ + Certificate: certBytes, + }, + }, + }, + }) + } + + clientKeyExchange := &handshake.MessageClientKeyExchange{} + if cfg.localPSKCallback == nil { + clientKeyExchange.PublicKey = state.localKeypair.PublicKey + } else { + clientKeyExchange.IdentityHint = cfg.localPSKIdentityHint + } + + 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(c, state, cfg, &handshake.MessageServerKeyExchange{}) + if err != nil { + return nil, alertPtr, err + } + } else { + rawHandshake := &handshake.Handshake{} + 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) + 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 := initalizeCipherSuite(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 && len(cfg.localCertificates) > 0 { + 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(cfg.localSignatureSchemes, privateKey) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err + } + + certVerify, err := generateCertificateVerify(plainText, privateKey, signatureHashAlgo.Hash) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.localCertificatesVerify = certVerify + + p := &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, p) + + 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++ // 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, + }, + }, + }, + shouldEncrypt: true, + resetLocalSequenceNumber: true, + }) + + return pkts, nil, nil +} + +func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeConfig, h *handshake.MessageServerKeyExchange, sendingPlainText []byte) (*alert.Alert, error) { //nolint:gocognit + if state.cipherSuite.IsInitialized() { + return nil, nil + } + + 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 { + // Verify that the pair of hash algorithm and signiture is listed. + var validSignatureScheme bool + for _, ss := range cfg.localSignatureSchemes { + if ss.Hash == h.HashAlgorithm && ss.Signature == h.SignatureAlgorithm { + validSignatureScheme = true + break + } + } + if !validSignatureScheme { + return &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes + } + + expectedMsg := valueKeyMessage(clientRandom[:], serverRandom[:], h.PublicKey, h.NamedCurve) + if err = verifyKeySignature(expectedMsg, h.Signature, h.HashAlgorithm, state.PeerCertificates); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + var chains [][]*x509.Certificate + if !cfg.insecureSkipVerify { + if chains, err = verifyServerCert(state.PeerCertificates, cfg.rootCAs, cfg.serverName); 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 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 +} diff --git a/dtls-2.0.9/flight6handler.go b/dtls-2.0.9/flight6handler.go new file mode 100644 index 0000000..10de5ad --- /dev/null +++ b/dtls-2.0.9/flight6handler.go @@ -0,0 +1,82 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight6Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + _, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-1, + 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 retransmitted the last flight. + return flight6, nil, nil +} + +func flight6Generate(c 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, + }, + }, + }, + shouldEncrypt: true, + resetLocalSequenceNumber: true, + }, + ) + return pkts, nil, nil +} diff --git a/dtls-2.0.9/flighthandler.go b/dtls-2.0.9/flighthandler.go new file mode 100644 index 0000000..b364c09 --- /dev/null +++ b/dtls-2.0.9/flighthandler.go @@ -0,0 +1,57 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/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) { + 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 flight5: + return flight5Parse, nil + case flight6: + return flight6Parse, nil + default: + return nil, errInvalidFlight + } +} + +func (f flightVal) getFlightGenerator() (gen flightGenerator, retransmit bool, err error) { + 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 flight5: + return flight5Generate, true, nil + case flight6: + return flight6Generate, true, nil + default: + return nil, false, errInvalidFlight + } +} diff --git a/dtls-2.0.9/fragment_buffer.go b/dtls-2.0.9/fragment_buffer.go new file mode 100644 index 0000000..0274993 --- /dev/null +++ b/dtls-2.0.9/fragment_buffer.go @@ -0,0 +1,111 @@ +package dtls + +import ( + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +type fragment struct { + recordLayerHeader recordlayer.Header + handshakeHeader handshake.Header + data []byte +} + +type fragmentBuffer struct { + // map of MessageSequenceNumbers that hold slices of fragments + cache map[uint16][]*fragment + + currentMessageSequenceNumber uint16 +} + +func newFragmentBuffer() *fragmentBuffer { + return &fragmentBuffer{cache: map[uint16][]*fragment{}} +} + +// 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) (bool, error) { + frag := new(fragment) + if err := frag.recordLayerHeader.Unmarshal(buf); err != nil { + return false, err + } + + // fragment isn't a handshake, we don't need to handle it + if frag.recordLayerHeader.ContentType != protocol.ContentTypeHandshake { + return false, nil + } + + for buf = buf[recordlayer.HeaderSize:]; len(buf) != 0; frag = new(fragment) { + if err := frag.handshakeHeader.Unmarshal(buf); err != nil { + return false, err + } + + if _, ok := f.cache[frag.handshakeHeader.MessageSequence]; !ok { + f.cache[frag.handshakeHeader.MessageSequence] = []*fragment{} + } + + // end index should be the length of handshake header but if the handshake + // was fragmented, we should keep them all + end := int(handshake.HeaderLength + frag.handshakeHeader.Length) + if size := len(buf); end > size { + end = size + } + + // Discard all headers, when rebuilding the packet we will re-build + frag.data = append([]byte{}, buf[handshake.HeaderLength:end]...) + f.cache[frag.handshakeHeader.MessageSequence] = append(f.cache[frag.handshakeHeader.MessageSequence], frag) + buf = buf[end:] + } + + return true, nil +} + +func (f *fragmentBuffer) pop() (content []byte, epoch uint16) { + frags, ok := f.cache[f.currentMessageSequenceNumber] + if !ok { + return nil, 0 + } + + // Go doesn't support recursive lambdas + var appendMessage func(targetOffset uint32) bool + + rawMessage := []byte{} + appendMessage = func(targetOffset uint32) bool { + for _, f := range frags { + if f.handshakeHeader.FragmentOffset == targetOffset { + fragmentEnd := (f.handshakeHeader.FragmentOffset + f.handshakeHeader.FragmentLength) + if fragmentEnd != f.handshakeHeader.Length { + if !appendMessage(fragmentEnd) { + return false + } + } + + rawMessage = append(f.data, rawMessage...) + return true + } + } + return false + } + + // Recursively collect up + if !appendMessage(0) { + return nil, 0 + } + + firstHeader := frags[0].handshakeHeader + firstHeader.FragmentOffset = 0 + firstHeader.FragmentLength = firstHeader.Length + + rawHeader, err := firstHeader.Marshal() + if err != nil { + return nil, 0 + } + + messageEpoch := frags[0].recordLayerHeader.Epoch + + delete(f.cache, f.currentMessageSequenceNumber) + f.currentMessageSequenceNumber++ + return append(rawHeader, rawMessage...), messageEpoch +} diff --git a/dtls-2.0.9/fragment_buffer_test.go b/dtls-2.0.9/fragment_buffer_test.go new file mode 100644 index 0000000..62c7ead --- /dev/null +++ b/dtls-2.0.9/fragment_buffer_test.go @@ -0,0 +1,101 @@ +package dtls + +import ( + "reflect" + "testing" +) + +func TestFragmentBuffer(t *testing.T) { + for _, test := range []struct { + Name string + In [][]byte + Expected [][]byte + Epoch uint16 + }{ + { + Name: "Single Fragment", + In: [][]byte{ + {0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}, + }, + Expected: [][]byte{ + {0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}, + }, + Epoch: 0, + }, + { + Name: "Single Fragment Epoch 3", + In: [][]byte{ + {0x16, 0xfe, 0xff, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}, + }, + Expected: [][]byte{ + {0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00}, + }, + Epoch: 3, + }, + { + Name: "Multiple Fragments", + In: [][]byte{ + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04}, + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09}, + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E}, + }, + Expected: [][]byte{ + {0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e}, + }, + Epoch: 0, + }, + { + Name: "Multiple Unordered Fragments", + In: [][]byte{ + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04}, + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E}, + {0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x81, 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09}, + }, + Expected: [][]byte{ + {0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e}, + }, + Epoch: 0, + }, + { + Name: "Multiple Handshakes in Signle Fragment", + In: [][]byte{ + { + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x30, /* record header */ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01, /*handshake msg 1*/ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01, /*handshake msg 2*/ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01, /*handshake msg 3*/ + }, + }, + Expected: [][]byte{ + {0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01}, + {0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01}, + {0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0x01, 0x01}, + }, + Epoch: 0, + }, + } { + fragmentBuffer := newFragmentBuffer() + for _, frag := range test.In { + status, err := fragmentBuffer.push(frag) + if err != nil { + t.Error(err) + } else if !status { + t.Errorf("fragmentBuffer didn't accept fragments for '%s'", test.Name) + } + } + + for _, expected := range test.Expected { + out, epoch := fragmentBuffer.pop() + if !reflect.DeepEqual(out, expected) { + t.Errorf("fragmentBuffer '%s' push/pop: got % 02x, want % 02x", test.Name, out, expected) + } + if epoch != test.Epoch { + t.Errorf("fragmentBuffer returned wrong epoch: got %d, want %d", epoch, test.Epoch) + } + } + + if frag, _ := fragmentBuffer.pop(); frag != nil { + t.Errorf("fragmentBuffer popped single buffer multiple times for '%s'", test.Name) + } + } +} diff --git a/dtls-2.0.9/fuzz.go b/dtls-2.0.9/fuzz.go new file mode 100644 index 0000000..56c1bf2 --- /dev/null +++ b/dtls-2.0.9/fuzz.go @@ -0,0 +1,38 @@ +// +build gofuzz + +package dtls + +import "fmt" + +func partialHeaderMismatch(a, b recordlayer.Header) bool { + // Ignoring content length for now. + a.contentLen = b.contentLen + return a != b +} + +func FuzzRecordLayer(data []byte) int { + var r recordLayer + if err := r.Unmarshal(data); err != nil { + return 0 + } + buf, err := r.Marshal() + if err != nil { + return 1 + } + if len(buf) == 0 { + panic("zero buff") // nolint + } + var nr recordLayer + if err = nr.Unmarshal(data); err != nil { + panic(err) // nolint + } + if partialHeaderMismatch(nr.recordlayer.Header, r.recordlayer.Header) { + panic( // nolint + fmt.Sprintf("header mismatch: %+v != %+v", + nr.recordlayer.Header, r.recordlayer.Header, + ), + ) + } + + return 1 +} diff --git a/dtls-2.0.9/fuzz/corpus/012178ca0830b7449ad370598d55873d81b95e40-25 b/dtls-2.0.9/fuzz/corpus/012178ca0830b7449ad370598d55873d81b95e40-25 new file mode 100644 index 0000000..f82ac9a Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/012178ca0830b7449ad370598d55873d81b95e40-25 differ diff --git a/dtls-2.0.9/fuzz/corpus/01277073b27ccc6925ce4c941527f7b7705c8311-1 b/dtls-2.0.9/fuzz/corpus/01277073b27ccc6925ce4c941527f7b7705c8311-1 new file mode 100644 index 0000000..4756c7a --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/01277073b27ccc6925ce4c941527f7b7705c8311-1 @@ -0,0 +1 @@ +12[A51 \ No newline at end of file diff --git a/dtls-2.0.9/fuzz/corpus/039192caed40959ac2f5c3254669312ba2dfbcad-12 b/dtls-2.0.9/fuzz/corpus/039192caed40959ac2f5c3254669312ba2dfbcad-12 new file mode 100644 index 0000000..3580e3c Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/039192caed40959ac2f5c3254669312ba2dfbcad-12 differ diff --git a/dtls-2.0.9/fuzz/corpus/03a9bad270cf32520b5c3e99add47c648ba6150f-7 b/dtls-2.0.9/fuzz/corpus/03a9bad270cf32520b5c3e99add47c648ba6150f-7 new file mode 100644 index 0000000..f3b959a Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/03a9bad270cf32520b5c3e99add47c648ba6150f-7 differ diff --git a/dtls-2.0.9/fuzz/corpus/048fcd45b732d5bed912e6652bc265a0adaf5664-26 b/dtls-2.0.9/fuzz/corpus/048fcd45b732d5bed912e6652bc265a0adaf5664-26 new file mode 100644 index 0000000..40c5ce2 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/048fcd45b732d5bed912e6652bc265a0adaf5664-26 differ diff --git a/dtls-2.0.9/fuzz/corpus/04a28c0806a91267f0576e11d042400f41dc538b-12 b/dtls-2.0.9/fuzz/corpus/04a28c0806a91267f0576e11d042400f41dc538b-12 new file mode 100644 index 0000000..a25e2a6 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/04a28c0806a91267f0576e11d042400f41dc538b-12 differ diff --git a/dtls-2.0.9/fuzz/corpus/04d00cfd50deb9ccd9d14be8c58f401a0414dad3-30 b/dtls-2.0.9/fuzz/corpus/04d00cfd50deb9ccd9d14be8c58f401a0414dad3-30 new file mode 100644 index 0000000..53a1acb Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/04d00cfd50deb9ccd9d14be8c58f401a0414dad3-30 differ diff --git a/dtls-2.0.9/fuzz/corpus/04e7f402f7d9f6ed2e664190dbd3267eddfddefa-6 b/dtls-2.0.9/fuzz/corpus/04e7f402f7d9f6ed2e664190dbd3267eddfddefa-6 new file mode 100644 index 0000000..3f7dfd3 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/04e7f402f7d9f6ed2e664190dbd3267eddfddefa-6 differ diff --git a/dtls-2.0.9/fuzz/corpus/057a8c627dc06c27296c8208265a9f8a32a8d4c2-19 b/dtls-2.0.9/fuzz/corpus/057a8c627dc06c27296c8208265a9f8a32a8d4c2-19 new file mode 100644 index 0000000..50846b2 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/057a8c627dc06c27296c8208265a9f8a32a8d4c2-19 differ diff --git a/dtls-2.0.9/fuzz/corpus/05a0d164b8e3ca08dc1bd077ce4aa4559731182b-15 b/dtls-2.0.9/fuzz/corpus/05a0d164b8e3ca08dc1bd077ce4aa4559731182b-15 new file mode 100644 index 0000000..aa77799 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/05a0d164b8e3ca08dc1bd077ce4aa4559731182b-15 differ diff --git a/dtls-2.0.9/fuzz/corpus/06148fe224720cd3a0497fc87f2b6bc5f004484a-30 b/dtls-2.0.9/fuzz/corpus/06148fe224720cd3a0497fc87f2b6bc5f004484a-30 new file mode 100644 index 0000000..f80886b Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/06148fe224720cd3a0497fc87f2b6bc5f004484a-30 differ diff --git a/dtls-2.0.9/fuzz/corpus/078c2bd97a33002242f9d5ac0a95970c9432124a-31 b/dtls-2.0.9/fuzz/corpus/078c2bd97a33002242f9d5ac0a95970c9432124a-31 new file mode 100644 index 0000000..ac541d3 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/078c2bd97a33002242f9d5ac0a95970c9432124a-31 differ diff --git a/dtls-2.0.9/fuzz/corpus/07ff33058f3c6732b9439f7d5c2bd50bb46adb31-20 b/dtls-2.0.9/fuzz/corpus/07ff33058f3c6732b9439f7d5c2bd50bb46adb31-20 new file mode 100644 index 0000000..dd34c82 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/07ff33058f3c6732b9439f7d5c2bd50bb46adb31-20 differ diff --git a/dtls-2.0.9/fuzz/corpus/08f2f7719e35261f615174917101cba578892f43-11 b/dtls-2.0.9/fuzz/corpus/08f2f7719e35261f615174917101cba578892f43-11 new file mode 100644 index 0000000..c6911cb Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/08f2f7719e35261f615174917101cba578892f43-11 differ diff --git a/dtls-2.0.9/fuzz/corpus/09b742837cf0d26ddecb5dbf536d91db6d1e9855-12 b/dtls-2.0.9/fuzz/corpus/09b742837cf0d26ddecb5dbf536d91db6d1e9855-12 new file mode 100644 index 0000000..c419031 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/09b742837cf0d26ddecb5dbf536d91db6d1e9855-12 differ diff --git a/dtls-2.0.9/fuzz/corpus/0a3bff70743f3cc7ecdc293887c10e14e152dec2-19 b/dtls-2.0.9/fuzz/corpus/0a3bff70743f3cc7ecdc293887c10e14e152dec2-19 new file mode 100644 index 0000000..73bb16a Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/0a3bff70743f3cc7ecdc293887c10e14e152dec2-19 differ diff --git a/dtls-2.0.9/fuzz/corpus/11e7b0e2a84f99b2f3f367cf546dde345bba563f-15 b/dtls-2.0.9/fuzz/corpus/11e7b0e2a84f99b2f3f367cf546dde345bba563f-15 new file mode 100644 index 0000000..3f8d0aa Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/11e7b0e2a84f99b2f3f367cf546dde345bba563f-15 differ diff --git a/dtls-2.0.9/fuzz/corpus/136a342418a743d6167ef2b44e657c82427469b8-35 b/dtls-2.0.9/fuzz/corpus/136a342418a743d6167ef2b44e657c82427469b8-35 new file mode 100644 index 0000000..fbfb7c0 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/136a342418a743d6167ef2b44e657c82427469b8-35 differ diff --git a/dtls-2.0.9/fuzz/corpus/137e470b38deeeac3586025e0e6e2702117e26e6 b/dtls-2.0.9/fuzz/corpus/137e470b38deeeac3586025e0e6e2702117e26e6 new file mode 100644 index 0000000..7af9349 --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/137e470b38deeeac3586025e0e6e2702117e26e6 @@ -0,0 +1 @@ +864797660130 \ No newline at end of file diff --git a/dtls-2.0.9/fuzz/corpus/156c962d90205b0c4afa3394de42d56967dfc7ee-14 b/dtls-2.0.9/fuzz/corpus/156c962d90205b0c4afa3394de42d56967dfc7ee-14 new file mode 100644 index 0000000..aa1f91d Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/156c962d90205b0c4afa3394de42d56967dfc7ee-14 differ diff --git a/dtls-2.0.9/fuzz/corpus/17863d02affd5fc60da97a59318b3f7014f93a9f-36 b/dtls-2.0.9/fuzz/corpus/17863d02affd5fc60da97a59318b3f7014f93a9f-36 new file mode 100644 index 0000000..0736720 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/17863d02affd5fc60da97a59318b3f7014f93a9f-36 differ diff --git a/dtls-2.0.9/fuzz/corpus/1841fb69e960e2d6ce1d19c6264e70b5606bfa39-32 b/dtls-2.0.9/fuzz/corpus/1841fb69e960e2d6ce1d19c6264e70b5606bfa39-32 new file mode 100644 index 0000000..7db3310 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/1841fb69e960e2d6ce1d19c6264e70b5606bfa39-32 differ diff --git a/dtls-2.0.9/fuzz/corpus/1a460400f96b0b40872eac2daed7c1db2e8f9843-11 b/dtls-2.0.9/fuzz/corpus/1a460400f96b0b40872eac2daed7c1db2e8f9843-11 new file mode 100644 index 0000000..fdcf4dc Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/1a460400f96b0b40872eac2daed7c1db2e8f9843-11 differ diff --git a/dtls-2.0.9/fuzz/corpus/1c042652c21f2c6d7ffcb6b6e6be55fdf95a5dbb-30 b/dtls-2.0.9/fuzz/corpus/1c042652c21f2c6d7ffcb6b6e6be55fdf95a5dbb-30 new file mode 100644 index 0000000..7a20346 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/1c042652c21f2c6d7ffcb6b6e6be55fdf95a5dbb-30 differ diff --git a/dtls-2.0.9/fuzz/corpus/1d09cef95c3269d3e244f0008a4fc6dfefd1e2ad-9 b/dtls-2.0.9/fuzz/corpus/1d09cef95c3269d3e244f0008a4fc6dfefd1e2ad-9 new file mode 100644 index 0000000..f7c5523 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/1d09cef95c3269d3e244f0008a4fc6dfefd1e2ad-9 differ diff --git a/dtls-2.0.9/fuzz/corpus/22e3d3a8748eb152a65ee9ada8834f8a07b247f4-29 b/dtls-2.0.9/fuzz/corpus/22e3d3a8748eb152a65ee9ada8834f8a07b247f4-29 new file mode 100644 index 0000000..a8c217e Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/22e3d3a8748eb152a65ee9ada8834f8a07b247f4-29 differ diff --git a/dtls-2.0.9/fuzz/corpus/23ce064ef35c0204982d748c34850bfc9433beca-13 b/dtls-2.0.9/fuzz/corpus/23ce064ef35c0204982d748c34850bfc9433beca-13 new file mode 100644 index 0000000..1a992de Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/23ce064ef35c0204982d748c34850bfc9433beca-13 differ diff --git a/dtls-2.0.9/fuzz/corpus/23e0e1cbd88637fbb4a19fe44c5665dda52e4c89-1 b/dtls-2.0.9/fuzz/corpus/23e0e1cbd88637fbb4a19fe44c5665dda52e4c89-1 new file mode 100644 index 0000000..bf83f49 --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/23e0e1cbd88637fbb4a19fe44c5665dda52e4c89-1 @@ -0,0 +1 @@ +8647996606130 \ No newline at end of file diff --git a/dtls-2.0.9/fuzz/corpus/2403e35492e1dc374b40bb2b4eda453c2e9612f2-21 b/dtls-2.0.9/fuzz/corpus/2403e35492e1dc374b40bb2b4eda453c2e9612f2-21 new file mode 100644 index 0000000..1d20649 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/2403e35492e1dc374b40bb2b4eda453c2e9612f2-21 differ diff --git a/dtls-2.0.9/fuzz/corpus/2438ed38ea739d8f57018f8de0a52f3e545ac760-18 b/dtls-2.0.9/fuzz/corpus/2438ed38ea739d8f57018f8de0a52f3e545ac760-18 new file mode 100644 index 0000000..6ebc62f Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/2438ed38ea739d8f57018f8de0a52f3e545ac760-18 differ diff --git a/dtls-2.0.9/fuzz/corpus/256b14a77bc0439a14908b6fa00afb348dde3af4-17 b/dtls-2.0.9/fuzz/corpus/256b14a77bc0439a14908b6fa00afb348dde3af4-17 new file mode 100644 index 0000000..fc08e43 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/256b14a77bc0439a14908b6fa00afb348dde3af4-17 differ diff --git a/dtls-2.0.9/fuzz/corpus/27702a0157f6eeb426aef4d5789b380d7b23801e-35 b/dtls-2.0.9/fuzz/corpus/27702a0157f6eeb426aef4d5789b380d7b23801e-35 new file mode 100644 index 0000000..64b2027 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/27702a0157f6eeb426aef4d5789b380d7b23801e-35 differ diff --git a/dtls-2.0.9/fuzz/corpus/29accdef171829b8dc0dba39d24acf913e13a31f-20 b/dtls-2.0.9/fuzz/corpus/29accdef171829b8dc0dba39d24acf913e13a31f-20 new file mode 100644 index 0000000..e0fd446 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/29accdef171829b8dc0dba39d24acf913e13a31f-20 differ diff --git a/dtls-2.0.9/fuzz/corpus/2ad24ef4188d2626e363cb12c5242fa96abfa7a3-13 b/dtls-2.0.9/fuzz/corpus/2ad24ef4188d2626e363cb12c5242fa96abfa7a3-13 new file mode 100644 index 0000000..8038d1a Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/2ad24ef4188d2626e363cb12c5242fa96abfa7a3-13 differ diff --git a/dtls-2.0.9/fuzz/corpus/2db7497fc9f463803d041365e337cccd7e74111a-18 b/dtls-2.0.9/fuzz/corpus/2db7497fc9f463803d041365e337cccd7e74111a-18 new file mode 100644 index 0000000..558ffdb Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/2db7497fc9f463803d041365e337cccd7e74111a-18 differ diff --git a/dtls-2.0.9/fuzz/corpus/30b9805b33c0d67926cbb5ab174508797eb7b7a7-17 b/dtls-2.0.9/fuzz/corpus/30b9805b33c0d67926cbb5ab174508797eb7b7a7-17 new file mode 100644 index 0000000..95b5ea5 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/30b9805b33c0d67926cbb5ab174508797eb7b7a7-17 differ diff --git a/dtls-2.0.9/fuzz/corpus/3105d624d1010500139670e332bd50771c112fdd-17 b/dtls-2.0.9/fuzz/corpus/3105d624d1010500139670e332bd50771c112fdd-17 new file mode 100644 index 0000000..ac56ddc Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3105d624d1010500139670e332bd50771c112fdd-17 differ diff --git a/dtls-2.0.9/fuzz/corpus/32b051a5ed27cbcb3c1689adbf51c4223e58f9bc-36 b/dtls-2.0.9/fuzz/corpus/32b051a5ed27cbcb3c1689adbf51c4223e58f9bc-36 new file mode 100644 index 0000000..f5138d9 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/32b051a5ed27cbcb3c1689adbf51c4223e58f9bc-36 differ diff --git a/dtls-2.0.9/fuzz/corpus/340161bf9f51d50c47d1853eb5d4fcac06914900-12 b/dtls-2.0.9/fuzz/corpus/340161bf9f51d50c47d1853eb5d4fcac06914900-12 new file mode 100644 index 0000000..f832cf5 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/340161bf9f51d50c47d1853eb5d4fcac06914900-12 differ diff --git a/dtls-2.0.9/fuzz/corpus/371f95aa3e615531b896c89647e6ce67586e082e-15 b/dtls-2.0.9/fuzz/corpus/371f95aa3e615531b896c89647e6ce67586e082e-15 new file mode 100644 index 0000000..9b65867 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/371f95aa3e615531b896c89647e6ce67586e082e-15 differ diff --git a/dtls-2.0.9/fuzz/corpus/386d1a6c0d51af038a3b2d3adba6eb15d8e3fe0a-23 b/dtls-2.0.9/fuzz/corpus/386d1a6c0d51af038a3b2d3adba6eb15d8e3fe0a-23 new file mode 100644 index 0000000..c5914c7 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/386d1a6c0d51af038a3b2d3adba6eb15d8e3fe0a-23 differ diff --git a/dtls-2.0.9/fuzz/corpus/3929563fe81b960a338a68a87a60e1940ac7f14e-34 b/dtls-2.0.9/fuzz/corpus/3929563fe81b960a338a68a87a60e1940ac7f14e-34 new file mode 100644 index 0000000..552a00a Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3929563fe81b960a338a68a87a60e1940ac7f14e-34 differ diff --git a/dtls-2.0.9/fuzz/corpus/3be9ff705b7c6d24ba58057e44fe7f51d0b0aa54-30 b/dtls-2.0.9/fuzz/corpus/3be9ff705b7c6d24ba58057e44fe7f51d0b0aa54-30 new file mode 100644 index 0000000..0a09ce8 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3be9ff705b7c6d24ba58057e44fe7f51d0b0aa54-30 differ diff --git a/dtls-2.0.9/fuzz/corpus/3eb3261e52074eceab2d28b5eee628d3ec213a84-14 b/dtls-2.0.9/fuzz/corpus/3eb3261e52074eceab2d28b5eee628d3ec213a84-14 new file mode 100644 index 0000000..c1d6dd4 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3eb3261e52074eceab2d28b5eee628d3ec213a84-14 differ diff --git a/dtls-2.0.9/fuzz/corpus/3f88c87cc5fe3fff5a45dc1916eed2fdcfe20d57-13 b/dtls-2.0.9/fuzz/corpus/3f88c87cc5fe3fff5a45dc1916eed2fdcfe20d57-13 new file mode 100644 index 0000000..0da0cc3 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3f88c87cc5fe3fff5a45dc1916eed2fdcfe20d57-13 differ diff --git a/dtls-2.0.9/fuzz/corpus/3f928478ccaf16b9685071b91f52d5e0e6bc71c1-38 b/dtls-2.0.9/fuzz/corpus/3f928478ccaf16b9685071b91f52d5e0e6bc71c1-38 new file mode 100644 index 0000000..c19a284 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/3f928478ccaf16b9685071b91f52d5e0e6bc71c1-38 differ diff --git a/dtls-2.0.9/fuzz/corpus/42ab249f3ceb17939f5fcab757894b22d94a86a8-22 b/dtls-2.0.9/fuzz/corpus/42ab249f3ceb17939f5fcab757894b22d94a86a8-22 new file mode 100644 index 0000000..322bf2c Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/42ab249f3ceb17939f5fcab757894b22d94a86a8-22 differ diff --git a/dtls-2.0.9/fuzz/corpus/42dbe1a681da3f7e48d18c53ab26b5893f3ea2ac-9 b/dtls-2.0.9/fuzz/corpus/42dbe1a681da3f7e48d18c53ab26b5893f3ea2ac-9 new file mode 100644 index 0000000..c7c2bed Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/42dbe1a681da3f7e48d18c53ab26b5893f3ea2ac-9 differ diff --git a/dtls-2.0.9/fuzz/corpus/471c2a2e1065b2c0f6040b286eebbca70e3742c6-10 b/dtls-2.0.9/fuzz/corpus/471c2a2e1065b2c0f6040b286eebbca70e3742c6-10 new file mode 100644 index 0000000..1b5fa66 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/471c2a2e1065b2c0f6040b286eebbca70e3742c6-10 differ diff --git a/dtls-2.0.9/fuzz/corpus/4735f3fc147ee436f8c02c24b9c40b4ee4cb1265-7 b/dtls-2.0.9/fuzz/corpus/4735f3fc147ee436f8c02c24b9c40b4ee4cb1265-7 new file mode 100644 index 0000000..66de70c Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/4735f3fc147ee436f8c02c24b9c40b4ee4cb1265-7 differ diff --git a/dtls-2.0.9/fuzz/corpus/48e4ba16b5626f66169cf52fb35054ae32f1037e-27 b/dtls-2.0.9/fuzz/corpus/48e4ba16b5626f66169cf52fb35054ae32f1037e-27 new file mode 100644 index 0000000..6c37949 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/48e4ba16b5626f66169cf52fb35054ae32f1037e-27 differ diff --git a/dtls-2.0.9/fuzz/corpus/4be120299b63639b4c203c93da101e2db703839a-26 b/dtls-2.0.9/fuzz/corpus/4be120299b63639b4c203c93da101e2db703839a-26 new file mode 100644 index 0000000..f8da2d6 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/4be120299b63639b4c203c93da101e2db703839a-26 differ diff --git a/dtls-2.0.9/fuzz/corpus/4cdafe201d691c06b529689668d52106a3e98dfa-22 b/dtls-2.0.9/fuzz/corpus/4cdafe201d691c06b529689668d52106a3e98dfa-22 new file mode 100644 index 0000000..2fb9f62 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/4cdafe201d691c06b529689668d52106a3e98dfa-22 differ diff --git a/dtls-2.0.9/fuzz/corpus/4d79d6a303e57c882d1d329ad4e3f091dd60e7ff-20 b/dtls-2.0.9/fuzz/corpus/4d79d6a303e57c882d1d329ad4e3f091dd60e7ff-20 new file mode 100644 index 0000000..672fb79 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/4d79d6a303e57c882d1d329ad4e3f091dd60e7ff-20 differ diff --git a/dtls-2.0.9/fuzz/corpus/509dbda3f391113a75c8309028bf59c0f107ac52-30 b/dtls-2.0.9/fuzz/corpus/509dbda3f391113a75c8309028bf59c0f107ac52-30 new file mode 100644 index 0000000..401db35 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/509dbda3f391113a75c8309028bf59c0f107ac52-30 differ diff --git a/dtls-2.0.9/fuzz/corpus/52aecd8762579fcaa1b5f26b152840f899683660-17 b/dtls-2.0.9/fuzz/corpus/52aecd8762579fcaa1b5f26b152840f899683660-17 new file mode 100644 index 0000000..9483584 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/52aecd8762579fcaa1b5f26b152840f899683660-17 differ diff --git a/dtls-2.0.9/fuzz/corpus/545ad51188a5d270eafe4733272be18ac1769c21-1 b/dtls-2.0.9/fuzz/corpus/545ad51188a5d270eafe4733272be18ac1769c21-1 new file mode 100644 index 0000000..e06cbd9 --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/545ad51188a5d270eafe4733272be18ac1769c21-1 @@ -0,0 +1 @@ +/ソソY \ No newline at end of file diff --git a/dtls-2.0.9/fuzz/corpus/5642ffc103d245461d8e754281bea517ff54ed85-17 b/dtls-2.0.9/fuzz/corpus/5642ffc103d245461d8e754281bea517ff54ed85-17 new file mode 100644 index 0000000..f2d2e99 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/5642ffc103d245461d8e754281bea517ff54ed85-17 differ diff --git a/dtls-2.0.9/fuzz/corpus/57d1652be22f597708e8099e2d23e8e4b00b0f89-33 b/dtls-2.0.9/fuzz/corpus/57d1652be22f597708e8099e2d23e8e4b00b0f89-33 new file mode 100644 index 0000000..620e248 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/57d1652be22f597708e8099e2d23e8e4b00b0f89-33 differ diff --git a/dtls-2.0.9/fuzz/corpus/59d6ef268e83be801c670340b2383a5a732308cb-8 b/dtls-2.0.9/fuzz/corpus/59d6ef268e83be801c670340b2383a5a732308cb-8 new file mode 100644 index 0000000..91e2853 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/59d6ef268e83be801c670340b2383a5a732308cb-8 differ diff --git a/dtls-2.0.9/fuzz/corpus/5b3cbe41487f4f9f5e728a86adce154ebd73fbe0-9 b/dtls-2.0.9/fuzz/corpus/5b3cbe41487f4f9f5e728a86adce154ebd73fbe0-9 new file mode 100644 index 0000000..71470b0 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/5b3cbe41487f4f9f5e728a86adce154ebd73fbe0-9 differ diff --git a/dtls-2.0.9/fuzz/corpus/5c165fd943bcb6df518c71b149d5aed736237833-16 b/dtls-2.0.9/fuzz/corpus/5c165fd943bcb6df518c71b149d5aed736237833-16 new file mode 100644 index 0000000..31cd9b3 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/5c165fd943bcb6df518c71b149d5aed736237833-16 differ diff --git a/dtls-2.0.9/fuzz/corpus/5eeaf10bf3fbb5575a63e054fd377645b5f45de5-3 b/dtls-2.0.9/fuzz/corpus/5eeaf10bf3fbb5575a63e054fd377645b5f45de5-3 new file mode 100644 index 0000000..224b46e --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/5eeaf10bf3fbb5575a63e054fd377645b5f45de5-3 @@ -0,0 +1 @@ +}\v/ \ No newline at end of file diff --git a/dtls-2.0.9/fuzz/corpus/64c5404b7e07af41448c99eadd4ded3a1572b503-9 b/dtls-2.0.9/fuzz/corpus/64c5404b7e07af41448c99eadd4ded3a1572b503-9 new file mode 100644 index 0000000..f887cb3 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/64c5404b7e07af41448c99eadd4ded3a1572b503-9 differ diff --git a/dtls-2.0.9/fuzz/corpus/6926133d1d407a21e5e57ed4ec71583b8f4650ab-16 b/dtls-2.0.9/fuzz/corpus/6926133d1d407a21e5e57ed4ec71583b8f4650ab-16 new file mode 100644 index 0000000..04d21c8 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/6926133d1d407a21e5e57ed4ec71583b8f4650ab-16 differ diff --git a/dtls-2.0.9/fuzz/corpus/6998ed50de84d0a1e2250af37ef989f866392d8e-7 b/dtls-2.0.9/fuzz/corpus/6998ed50de84d0a1e2250af37ef989f866392d8e-7 new file mode 100644 index 0000000..4fcf44d Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/6998ed50de84d0a1e2250af37ef989f866392d8e-7 differ diff --git a/dtls-2.0.9/fuzz/corpus/6a823391df6589e83b50fbf6ad7ec4a61edb34c5-35 b/dtls-2.0.9/fuzz/corpus/6a823391df6589e83b50fbf6ad7ec4a61edb34c5-35 new file mode 100644 index 0000000..fe27af2 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/6a823391df6589e83b50fbf6ad7ec4a61edb34c5-35 differ diff --git a/dtls-2.0.9/fuzz/corpus/6af8fabbde43b2d6bb76502831dbd8c0d1dea233-36 b/dtls-2.0.9/fuzz/corpus/6af8fabbde43b2d6bb76502831dbd8c0d1dea233-36 new file mode 100644 index 0000000..aacbe8b Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/6af8fabbde43b2d6bb76502831dbd8c0d1dea233-36 differ diff --git a/dtls-2.0.9/fuzz/corpus/6b33f20c523b6d32a26863fa65923e66ab555408-3 b/dtls-2.0.9/fuzz/corpus/6b33f20c523b6d32a26863fa65923e66ab555408-3 new file mode 100644 index 0000000..e61c359 Binary files /dev/null and b/dtls-2.0.9/fuzz/corpus/6b33f20c523b6d32a26863fa65923e66ab555408-3 differ diff --git a/dtls-2.0.9/fuzz/corpus/6bf06a9be690f993286b45425cb88b8331876fe1-1 b/dtls-2.0.9/fuzz/corpus/6bf06a9be690f993286b45425cb88b8331876fe1-1 new file mode 100644 index 0000000..c9231f2 --- /dev/null +++ b/dtls-2.0.9/fuzz/corpus/6bf06a9be690f993286b45425cb88b8331876fe1-1 @@ -0,0 +1 @@ +1 | 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 + state *State + cache *handshakeCache + cfg *handshakeConfig + closed chan struct{} +} + +type handshakeConfig struct { + localPSKCallback PSKCallback + localPSKIdentityHint []byte + localCiscoCompatCallback PSKCallback // TODO add cisco anyconnect support + localCipherSuites []CipherSuite // Available CipherSuites + localSignatureSchemes []signaturehash.Algorithm // Available signature schemes + extendedMasterSecret ExtendedMasterSecretType // Policy for the Extended Master Support extension + localSRTPProtectionProfiles []SRTPProtectionProfile // Available SRTPProtectionProfiles, if empty no SRTP support + serverName 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 + rootCAs *x509.CertPool + clientCAs *x509.CertPool + retransmitInterval time.Duration + customCipherSuites func() []CipherSuite + + onFlightState func(flightVal, handshakeState) + log logging.LeveledLogger + keyLogWriter io.Writer + + initialEpoch uint16 + + mu sync.Mutex +} + +type flightConn interface { + notify(ctx context.Context, level alert.Level, desc alert.Description) error + writePackets(context.Context, []*packet) error + recvHandshake() <-chan chan struct{} + setLocalEpoch(epoch uint16) + handleQueuedPackets(context.Context) error +} + +func (c *handshakeConfig) writeKeyLog(label string, clientRandom, secret []byte) { + if c.keyLogWriter == nil { + return + } + c.mu.Lock() + defer c.mu.Unlock() + _, err := c.keyLogWriter.Write([]byte(fmt.Sprintf("%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, + closed: make(chan struct{}), + } +} + +func (s *handshakeFSM) Run(ctx context.Context, c 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, c) + case handshakeSending: + state, err = s.send(ctx, c) + case handshakeWaiting: + state, err = s.wait(ctx, c) + case handshakeFinished: + state, err = s.finish(ctx, c) + default: + return errInvalidFSMTransition + } + if err != nil { + return err + } + + // TODO 添加 CiscoCompat 支持 + if s.cfg.localCiscoCompatCallback != nil { + if s.currentFlight == flight4 && state == handshakeWaiting { + s.currentFlight = flight6 + state = handshakePreparing + } + } + } +} + +func (s *handshakeFSM) Done() <-chan struct{} { + return s.closed +} + +func (s *handshakeFSM) prepare(ctx context.Context, c flightConn) (handshakeState, error) { + s.flights = nil + // Prepare flights + var ( + a *alert.Alert + err error + pkts []*packet + ) + gen, retransmit, errFlight := s.currentFlight.getFlightGenerator() + if errFlight != nil { + err = errFlight + a = &alert.Alert{Level: alert.Fatal, Description: alert.InternalError} + } else { + pkts, a, err = gen(c, s.state, s.cache, s.cfg) + s.retransmit = retransmit + } + if a != nil { + if alertErr := c.notify(ctx, a.Level, a.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) + s.state.handshakeSendSequence++ + } + } + if epoch != nextEpoch { + s.cfg.log.Tracef("[handshake:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.isClient), nextEpoch) + c.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, c flightConn) (handshakeState, error) { //nolint:gocognit + parse, errFlight := s.currentFlight.getFlightParser() + if errFlight != nil { + if alertErr := c.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { + if errFlight != nil { + return handshakeErrored, alertErr + } + } + return handshakeErrored, errFlight + } + + retransmitTimer := time.NewTimer(s.cfg.retransmitInterval) + for { + select { + case done := <-c.recvHandshake(): + nextFlight, alert, err := parse(ctx, c, s.state, s.cache, s.cfg) + close(done) + if alert != nil { + if alertErr := c.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 + } + return handshakeSending, nil + case <-ctx.Done(): + return handshakeErrored, ctx.Err() + } + } +} + +func (s *handshakeFSM) finish(ctx context.Context, c flightConn) (handshakeState, error) { + parse, errFlight := s.currentFlight.getFlightParser() + if errFlight != nil { + if alertErr := c.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { + if errFlight != nil { + return handshakeErrored, alertErr + } + } + return handshakeErrored, errFlight + } + + retransmitTimer := time.NewTimer(s.cfg.retransmitInterval) + select { + case done := <-c.recvHandshake(): + nextFlight, alert, err := parse(ctx, c, s.state, s.cache, s.cfg) + close(done) + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err != nil { + err = alertErr + } + } + } + if err != nil { + return handshakeErrored, err + } + if nextFlight == 0 { + break + } + <-retransmitTimer.C + // Retransmit last flight + return handshakeSending, nil + + case <-ctx.Done(): + return handshakeErrored, ctx.Err() + } + return handshakeFinished, nil +} diff --git a/dtls-2.0.9/handshaker_test.go b/dtls-2.0.9/handshaker_test.go new file mode 100644 index 0000000..d26b987 --- /dev/null +++ b/dtls-2.0.9/handshaker_test.go @@ -0,0 +1,277 @@ +package dtls + +import ( + "bytes" + "context" + "crypto/tls" + "sync" + "testing" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + "github.com/pion/logging" + "github.com/pion/transport/test" +) + +const nonZeroRetransmitInterval = 100 * time.Millisecond + +// Test that writes to the key log are in the correct format and only applies +// when a key log writer is given. +func TestWriteKeyLog(t *testing.T) { + var buf bytes.Buffer + cfg := handshakeConfig{ + keyLogWriter: &buf, + } + cfg.writeKeyLog("LABEL", []byte{0xAA, 0xBB, 0xCC}, []byte{0xDD, 0xEE, 0xFF}) + + // Secrets follow the format