mirror of
https://github.com/bjdgyc/anylink.git
synced 2025-09-28 16:15:17 +08:00
Compare commits
424 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
edc7a4a4a3 | ||
|
17492d8172 | ||
|
65de1a58cf | ||
|
dfb25718f7 | ||
|
3deda4d77f | ||
|
2af524f87b | ||
|
1b6abeb849 | ||
|
e3b303744b | ||
|
d3f16eb2ad | ||
|
64404ea94b | ||
|
ededfddff4 | ||
|
8a3d34b737 | ||
|
7c040e2a0f | ||
|
3d03f6adb8 | ||
|
5d24eda7fc | ||
|
ea92857524 | ||
|
b521dddb98 | ||
|
2bd94aef2b | ||
|
3879c3a4bc | ||
|
aa2b89855f | ||
|
9e1969e3d0 | ||
|
5b1d86282a | ||
|
bfc39fe4ea | ||
|
9019a5f03a | ||
|
38d268e999 | ||
|
57990d3d2a | ||
|
6788a875a2 | ||
|
a9ad21b3b5 | ||
|
43ca09e985 | ||
|
b7da567cee | ||
|
6eea265b15 | ||
|
06c8ee1197 | ||
|
ebc7cc85c0 | ||
|
012f636cf7 | ||
|
4f9cc2074a | ||
|
bbc5877eb9 | ||
|
c6b85c7d66 | ||
|
8e843d5eae | ||
|
7b9be9377f | ||
|
f03264faf3 | ||
|
b19ff321ad | ||
|
f6980261d4 | ||
|
7651b69ed6 | ||
|
2af2d273e4 | ||
|
ff54abc5d5 | ||
|
a168c96a93 | ||
|
6127c41aea | ||
|
da1d6c6c6d | ||
|
08de4fe086 | ||
|
7714c2a3e8 | ||
|
78a8b06467 | ||
|
28ffda2371 | ||
|
c23b120e90 | ||
|
287355de54 | ||
|
01f90e5bb5 | ||
|
91a9190379 | ||
|
0a9fe8f96c | ||
|
254110ebff | ||
|
9c706a7d0d | ||
|
d228e224cd | ||
|
6e95ea5441 | ||
|
ce61401304 | ||
|
d7d2696790 | ||
|
9a6aaa87e5 | ||
|
e31b5d83d4 | ||
|
fc2920e140 | ||
|
d36e2fe85a | ||
|
14efb14a9a | ||
|
92de727db8 | ||
|
c63e4f33d5 | ||
|
60095fbc9b | ||
|
fe9b84ce98 | ||
|
fd5ec7f86a | ||
|
50bc864fdd | ||
|
a9e798f203 | ||
|
165d4ef8a0 | ||
|
65c24c4e27 | ||
|
b81bc5c283 | ||
|
b52b8598df | ||
|
22fda0f6a1 | ||
|
fed9066f22 | ||
|
91ce4752f3 | ||
|
c05ec9ab36 | ||
|
6ee80d32ea | ||
|
cc5aff08ad | ||
|
690b4460ad | ||
|
9dff39d299 | ||
|
638c601c02 | ||
|
c2129af104 | ||
|
bc9248e16b | ||
|
8028b73d81 | ||
|
214311e80c | ||
|
43de8148a0 | ||
|
150fff328f | ||
|
609a893feb | ||
|
9cb8c97af9 | ||
|
8798de0d6d | ||
|
26d20c0b40 | ||
|
19e99b7648 | ||
|
5dc8114167 | ||
|
4b83bd7ccf | ||
|
bc7c61c337 | ||
|
b3e7212b03 | ||
|
748adadd1e | ||
|
c646f79ef8 | ||
|
061f6f222b | ||
|
9bac773961 | ||
|
4d15fe286a | ||
|
df52087473 | ||
|
9533ecd6c5 | ||
|
8608c6acee | ||
|
768e137ff9 | ||
|
ef314c891b | ||
|
4d9919d43c | ||
|
262ad49df6 | ||
|
6cfa92944c | ||
|
8ab46e3279 | ||
|
70c82b8baa | ||
|
29953911da | ||
|
273552ddfe | ||
|
710cfe4244 | ||
|
d5205c74cf | ||
|
3eb2af6155 | ||
|
5fabefd315 | ||
|
b84d10d64f | ||
|
b0f5456d68 | ||
|
1c846b90b8 | ||
|
c267553287 | ||
|
c8bde076d8 | ||
|
cff97d746c | ||
|
1582b46bb9 | ||
|
a3d5945611 | ||
|
b63b7936bc | ||
|
337ffeb1a2 | ||
|
279b6f87a0 | ||
|
59e3c9347b | ||
|
d51bc63419 | ||
|
d3f51a5af3 | ||
|
7f4b668dbf | ||
|
e8c121c6b1 | ||
|
7299c0e761 | ||
|
ea26eb1e24 | ||
|
5e109091f5 | ||
|
d00293aaf9 | ||
|
0aacc244c4 | ||
|
d7bcc7988c | ||
|
14eed7265c | ||
|
4f9a1c0484 | ||
|
62554cfba0 | ||
|
9809b407d5 | ||
|
9e53ec289c | ||
|
a600be6949 | ||
|
e909ca552c | ||
|
ef5cad6c7a | ||
|
7ac34935a6 | ||
|
42de009e30 | ||
|
ae7fe993f7 | ||
|
b741f58189 | ||
|
f12ac2f4d8 | ||
|
68fa1110f1 | ||
|
ce232937ec | ||
|
094d19be00 | ||
|
32026eff2c | ||
|
6b6e963458 | ||
|
7a290fe807 | ||
|
fdb16a4cb6 | ||
|
b06247201d | ||
|
d3ac346c85 | ||
|
8c9e371df8 | ||
|
a8b5bb4a68 | ||
|
ce1a6f2d8c | ||
|
73096eae13 | ||
|
24fc03f378 | ||
|
c26675206e | ||
|
ff018a718e | ||
|
5a60398435 | ||
|
32ce3f04d0 | ||
|
5ef13b1bff | ||
|
2bc2ade5d8 | ||
|
ea5e31fd39 | ||
|
50c30657ac | ||
|
b1ae25ae2f | ||
|
c8b34bd772 | ||
|
87dcc63b6f | ||
|
eb8d55040c | ||
|
46721b1453 | ||
|
36cb865bad | ||
|
4a7d534981 | ||
|
45ed1c34f9 | ||
|
c2ddb7331d | ||
|
2d375869df | ||
|
890ff5753f | ||
|
54be85ef03 | ||
|
2e47957f7f | ||
|
2b715774d8 | ||
|
69926a4ba1 | ||
|
6dcb79b30a | ||
|
16b0236c6f | ||
|
6fdc0e54d5 | ||
|
9f40bb40ef | ||
|
7de9d9f16d | ||
|
86ee88a20c | ||
|
5f5ab3fbca | ||
|
88dd3c8860 | ||
|
7032ebdc85 | ||
|
9fe31212e4 | ||
|
7b19010892 | ||
|
77518a3a0c | ||
|
c68374f4f0 | ||
|
a8d0a39ca0 | ||
|
a77fe77e79 | ||
|
9e58f3121f | ||
|
6915d94f31 | ||
|
a83999013e | ||
|
7e8eea5f80 | ||
|
6f7fcfc6ee | ||
|
8536e2f1d4 | ||
|
28489dcbc6 | ||
|
84286de8a4 | ||
|
522f723b51 | ||
|
c6ef0a28b4 | ||
|
ea7a487c26 | ||
|
e66a842e77 | ||
|
ef30451515 | ||
|
42f60a4d9d | ||
|
97840bef98 | ||
|
cad824d53c | ||
|
59d343f17c | ||
|
d6154bc621 | ||
|
d277f1084f | ||
|
e3b23c5391 | ||
|
b1890a2c8a | ||
|
bcd4fa1872 | ||
|
3a2482f1d6 | ||
|
adebd4b844 | ||
|
bf7491346a | ||
|
201a62144e | ||
|
3dbc369e6b | ||
|
812eb587bb | ||
|
ec1cf3fcda | ||
|
a533ee0a78 | ||
|
7b83154245 | ||
|
c74ba43cb0 | ||
|
7aa8dc0294 | ||
|
509a7f1ab1 | ||
|
8c6173d3d8 | ||
|
6a1aa35f18 | ||
|
ea5bc1088c | ||
|
3ba9ed8266 | ||
|
f71616c4eb | ||
|
2093bd0bef | ||
|
b8f0f4c618 | ||
|
df1fe6f838 | ||
|
82dfb5d04f | ||
|
2c8474a478 | ||
|
82ad948b08 | ||
|
ac0b9ee4ee | ||
|
3d88c617bf | ||
|
d1b209f18f | ||
|
219a74b118 | ||
|
83263ff635 | ||
|
6f875001f2 | ||
|
4a412fe0ee | ||
|
adb8b45985 | ||
|
a2a77d0852 | ||
|
3aecbda5f4 | ||
|
e8ec65dec2 | ||
|
e03bd60b1d | ||
|
c2642bc183 | ||
|
50fa86d780 | ||
|
427ad7f7f5 | ||
|
f6fa2fe46b | ||
|
290f78d7f3 | ||
|
edb2d2e19b | ||
|
e483da10e1 | ||
|
4c37ee5550 | ||
|
92f39dfc96 | ||
|
3c5acd31fb | ||
|
4e2ce12f6c | ||
|
19bda654ff | ||
|
8b1098917f | ||
|
68bb5d24b6 | ||
|
10ca7c9c85 | ||
|
a48f071e0a | ||
|
816dadae29 | ||
|
157837e100 | ||
|
56d3b16a10 | ||
|
11feb6d4a5 | ||
|
49db8ccff8 | ||
|
d5a729b4e4 | ||
|
48bebadc6b | ||
|
5586b27319 | ||
|
a5b4463280 | ||
|
38afab2440 | ||
|
0126333605 | ||
|
00281dbfa5 | ||
|
7b3507d962 | ||
|
f2c649d65b | ||
|
b5c14e5f29 | ||
|
5b220838d9 | ||
|
89e1828b5e | ||
|
b9b0fc811d | ||
|
091dc69f1d | ||
|
1547201049 | ||
|
af52cc21bc | ||
|
e72994c0b8 | ||
|
0dad4429a3 | ||
|
047e478a5a | ||
|
9ccaa039d0 | ||
|
ab9a1b97a7 | ||
|
b8d3eedd9d | ||
|
86a4ed8948 | ||
|
ffb529897f | ||
|
d5f88affcd | ||
|
6722541fa5 | ||
|
b9bd74ed1f | ||
|
b8c3d33f2e | ||
|
e761c1e111 | ||
|
1dceffc327 | ||
|
9f8601221d | ||
|
6a69377568 | ||
|
8837a07fac | ||
|
75a8358457 | ||
|
cd21dec605 | ||
|
1efd007e94 | ||
|
973541c132 | ||
|
dd8c114632 | ||
|
ab3b8b34d9 | ||
|
ecb5094780 | ||
|
a9e31a0a3b | ||
|
88e5546a3b | ||
|
5c41367383 | ||
|
b0569b33cc | ||
|
e90c2aae6c | ||
|
1ad19fe06b | ||
|
50121b8ff2 | ||
|
d1414c6b5d | ||
|
06931551d0 | ||
|
3aff1daaf2 | ||
|
918859cc62 | ||
|
9f98f71210 | ||
|
116c04f8b1 | ||
|
4b36577b00 | ||
|
f208be9126 | ||
|
2b9c0fdca8 | ||
|
04d025c68b | ||
|
7f1e769377 | ||
|
c76b074893 | ||
|
54f7a59a91 | ||
|
25cb1fc19a | ||
|
6a997bfd46 | ||
|
68076f58eb | ||
|
e24aa2d900 | ||
|
9dad2c08a5 | ||
|
8ede613488 | ||
|
f46a30488a | ||
|
b06c035cce | ||
|
730c34b2a4 | ||
|
c38f1e9b8c | ||
|
a450fe3eef | ||
|
e9c55a0853 | ||
|
f560fc459b | ||
|
d4f266de66 | ||
|
3e491d33c6 | ||
|
77e507fab7 | ||
|
9d5b9c4ded | ||
|
a0830f0440 | ||
|
3de75285de | ||
|
cde46c5765 | ||
|
f325970089 | ||
|
f6996363e3 | ||
|
90aa6c272d | ||
|
2964b34087 | ||
|
625e4eecf4 | ||
|
522be82a31 | ||
|
ccce143f85 | ||
|
01431b2230 | ||
|
7a92aa8dff | ||
|
2b580067a2 | ||
|
500a11612c | ||
|
64fc4d082e | ||
|
cf507d204b | ||
|
fd7242dbba | ||
|
9a712ca489 | ||
|
e44e1dcf2a | ||
|
fce96753c5 | ||
|
77efe7583d | ||
|
1cbe9bfc30 | ||
|
2e7afa9c35 | ||
|
a58507f5a6 | ||
|
1989b235fe | ||
|
21b047b307 | ||
|
2daad88159 | ||
|
7c86513b0c | ||
|
4b41e6c5b9 | ||
|
684fea69d0 | ||
|
85e2ba0b0f | ||
|
3fff44dde5 | ||
|
8b2b058450 | ||
|
f0305415ae | ||
|
ea84a29350 | ||
|
6c5969c5ea | ||
|
f267891f19 | ||
|
d1167650a3 | ||
|
f7c8fb8d9d | ||
|
e780afe18c | ||
|
aadfa7b70c | ||
|
73d5a1be69 | ||
|
3cb22172b1 | ||
|
f42176299d | ||
|
edcfd5d8f2 | ||
|
3697227f7a | ||
|
11cbfbc91d | ||
|
4518959bf9 | ||
|
4f1d345256 | ||
|
7fc9f73512 | ||
|
5c3dfeb3af | ||
|
ca4626879d | ||
|
a0065ade42 | ||
|
a47cdc9a00 | ||
|
c07914d568 | ||
|
df94afdfd0 | ||
|
b4f88e154c | ||
|
e4270f724b |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://github.com/bjdgyc/anylink/blob/main/doc/README.md'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
35
.github/ISSUE_TEMPLATE/00-question.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/00-question.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: 问题描述
|
||||
about: 问题描述
|
||||
title: "affected/package: "
|
||||
---
|
||||
|
||||
<!--
|
||||
请先填写下面的问题,再填写具体遇到的问题,感谢!
|
||||
-->
|
||||
|
||||
### 使用的anylink版本 ?
|
||||
|
||||
<pre>
|
||||
./anylink tool -v
|
||||
管理后台也可以查看
|
||||
</pre>
|
||||
|
||||
|
||||
### 使用操作系统的类型和版本?
|
||||
如: centos 7.9
|
||||
<pre>
|
||||
cat /etc/issue
|
||||
cat /etc/redhat-release
|
||||
|
||||
</pre>
|
||||
|
||||
### 使用linux 内核版本?
|
||||
<pre>
|
||||
uname -a
|
||||
|
||||
</pre>
|
||||
|
||||
### 具体遇到的问题,可上传截图
|
||||
|
||||
|
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -13,10 +13,10 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
branches: [ "main", "dev" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ dev ]
|
||||
branches: [ "main", "dev" ]
|
||||
schedule:
|
||||
- cron: '32 12 * * 5'
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -68,4 +68,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
48
Dockerfile
48
Dockerfile
@@ -1,48 +0,0 @@
|
||||
# web
|
||||
FROM node:lts-alpine as builder_node
|
||||
WORKDIR /web
|
||||
COPY ./web /web
|
||||
RUN npm install --registry=https://registry.npm.taobao.org \
|
||||
&& npm run build \
|
||||
&& ls /web/ui
|
||||
|
||||
# server
|
||||
FROM golang:1.16-alpine as builder_golang
|
||||
#TODO 本地打包时使用镜像
|
||||
ENV GOPROXY=https://goproxy.io
|
||||
ENV GOOS=linux
|
||||
WORKDIR /anylink
|
||||
COPY . /anylink
|
||||
COPY --from=builder_node /web/ui /anylink/server/ui
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache git gcc musl-dev
|
||||
RUN cd /anylink/server;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
||||
&& /anylink/server/anylink tool -v
|
||||
|
||||
# anylink
|
||||
FROM alpine
|
||||
LABEL maintainer="github.com/bjdgyc"
|
||||
|
||||
ENV IPV4_CIDR="192.168.10.0/24"
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder_golang /anylink/server/anylink /app/
|
||||
COPY docker_entrypoint.sh /app/
|
||||
|
||||
COPY ./server/conf /app/conf
|
||||
COPY ./server/files /app/conf/files
|
||||
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache bash iptables \
|
||||
&& chmod +x /app/docker_entrypoint.sh \
|
||||
&& ls /app
|
||||
|
||||
EXPOSE 443 8800
|
||||
|
||||
#CMD ["/app/anylink"]
|
||||
ENTRYPOINT ["/app/docker_entrypoint.sh"]
|
||||
|
674
LICENSE
674
LICENSE
@@ -1,21 +1,661 @@
|
||||
MIT License
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (c) 2020 bjdgyc
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
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:
|
||||
Preamble
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server 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.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
189
README.md
189
README.md
@@ -1,11 +1,12 @@
|
||||
# AnyLink
|
||||
|
||||
[](https://github.com/bjdgyc/anylink/actions)
|
||||
[](https://github.com/bjdgyc/anylink/actions)
|
||||
[](https://pkg.go.dev/github.com/bjdgyc/anylink)
|
||||
[](https://goreportcard.com/report/github.com/bjdgyc/anylink)
|
||||
[](https://codecov.io/gh/bjdgyc/anylink)
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/bjdgyc/anylink)
|
||||

|
||||
|
||||
AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。
|
||||
@@ -23,27 +24,56 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
|
||||
|
||||
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
|
||||
|
||||
AnyLink 服务端仅在 CentOS 7、Ubuntu 18.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
|
||||
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap
|
||||
功能、ip 设置命令。
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
|
||||
## Donate
|
||||
|
||||
> 如果您觉得 anylink 对你有帮助,欢迎给我们打赏,也是帮助 anylink 更好的发展。
|
||||
>
|
||||
> [查看打赏列表](doc/README.md)
|
||||
|
||||
<p>
|
||||
<img src="doc/screenshot/wxpay2.png" width="400" />
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
|
||||
> 没有编程基础的同学建议直接下载 release 包,从下面的地址下载 anylink-deploy.tar.gz
|
||||
>
|
||||
> https://github.com/bjdgyc/anylink/releases
|
||||
|
||||
### 使用问题
|
||||
|
||||
> 对于测试环境,可以使用 vpn.test.vqilu.cn 绑定host进行测试
|
||||
>
|
||||
> 对于线上环境,必须申请安全的 https 证书,不支持私有证书连接
|
||||
>
|
||||
> 服务端安装 yum install iproute 或者 apt-get install iproute2
|
||||
>
|
||||
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
|
||||
>
|
||||
> 其他问题 [前往查看](doc/question.md)
|
||||
>
|
||||
> 首次使用,请在浏览器访问 https://域名:443,浏览器提示安全后,在客户端输入 【域名:443】 即可
|
||||
|
||||
### 自行编译安装
|
||||
|
||||
> 需要提前安装好 golang >= 1.16 和 nodejs >= 14.x
|
||||
>
|
||||
> 使用客户端前,必须申请安全的 https 证书,不支持私有证书连接
|
||||
> 需要提前安装好 golang >= 1.19 和 nodejs >= 16.x 和 yarn >= v1.22.x
|
||||
|
||||
```shell
|
||||
git clone https://github.com/bjdgyc/anylink.git
|
||||
|
||||
# 编译参考软件版本
|
||||
# go 1.20.12
|
||||
# node v16.20.2
|
||||
# yarn 1.22.19
|
||||
|
||||
|
||||
cd anylink
|
||||
sh build.sh
|
||||
|
||||
@@ -52,10 +82,12 @@ cd anylink-deploy
|
||||
sudo ./anylink
|
||||
|
||||
# 默认管理后台访问地址
|
||||
# http://host:8800
|
||||
# 注意该host为anylink的内网ip,不能跟客户端请求的ip一样
|
||||
# https://host:8800
|
||||
# 默认账号 密码
|
||||
# admin 123456
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Feature
|
||||
@@ -64,18 +96,23 @@ sudo ./anylink
|
||||
- [x] TLS-TCP 通道
|
||||
- [x] DTLS-UDP 通道
|
||||
- [x] 兼容 AnyConnect
|
||||
- [x] 兼容 OpenConnect
|
||||
- [x] 基于 tun 设备的 nat 访问模式
|
||||
- [x] 基于 tap 设备的桥接访问模式
|
||||
- [x] 基于 macvtap 设备的桥接访问模式
|
||||
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
||||
- [x] 支持 [proxy protocol v1&v2](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
||||
- [x] 用户组支持
|
||||
- [x] 多用户支持
|
||||
- [x] 用户策略支持
|
||||
- [x] TOTP 令牌支持
|
||||
- [x] TOTP 令牌开关
|
||||
- [x] 流量速率限制
|
||||
- [x] 后台管理界面
|
||||
- [x] 访问权限管理
|
||||
- [x] IP 访问审计功能
|
||||
- [x] 域名动态拆分隧道(域名路由功能)
|
||||
- [x] radius认证支持
|
||||
- [x] LDAP认证支持
|
||||
- [ ] 基于 ipvtap 设备的桥接访问模式
|
||||
|
||||
## Config
|
||||
@@ -93,7 +130,7 @@ sudo ./anylink
|
||||
> 数据库配置示例
|
||||
|
||||
| db_type | db_source |
|
||||
| -------- | ------------------------------------------------------ |
|
||||
|----------|--------------------------------------------------------|
|
||||
| sqlite3 | ./conf/anylink.db |
|
||||
| mysql | user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8 |
|
||||
| postgres | user:password@localhost/anylink?sslmode=verify-full |
|
||||
@@ -106,34 +143,62 @@ sudo ./anylink
|
||||
|
||||
> 以下参数必须设置其中之一
|
||||
|
||||
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"` 等参数。 不同的参数需要对服务器做相应的设置。
|
||||
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"(不推荐)` 等参数。
|
||||
不同的参数需要对服务器做相应的设置。
|
||||
|
||||
建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到 IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap 模式,请确认虚拟机的网卡开启混杂模式。
|
||||
建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到
|
||||
IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap
|
||||
模式,请确认虚拟机的网卡开启混杂模式。
|
||||
|
||||
### tun 设置
|
||||
|
||||
1. 开启服务器转发
|
||||
|
||||
```shell
|
||||
# flie: /etc/sysctl.conf
|
||||
# file: /etc/sysctl.conf
|
||||
net.ipv4.ip_forward = 1
|
||||
|
||||
#执行如下命令
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
# 查看设置是否生效
|
||||
cat /proc/sys/net/ipv4/ip_forward
|
||||
```
|
||||
|
||||
2. 设置 nat 转发规则
|
||||
2.1 设置 nat 转发规则(二选一)
|
||||
|
||||
```shell
|
||||
systemctl stop firewalld.service
|
||||
systemctl disable firewalld.service
|
||||
|
||||
# 新版本支持自动设置nat转发,如有其他需求可以参考下面的命令配置
|
||||
|
||||
# 请根据服务器内网网卡替换 eth0
|
||||
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
|
||||
# iptables -t nat -A POSTROUTING -s 192.168.90.0/24 -o eth0 -j MASQUERADE
|
||||
# 如果执行第一个命令不生效,可以继续执行下面的命令
|
||||
# iptables -A FORWARD -i eth0 -s 192.168.10.0/24 -j ACCEPT
|
||||
# iptables -A FORWARD -i eth0 -s 192.168.90.0/24 -j ACCEPT
|
||||
# 查看设置是否生效
|
||||
iptables -nL -t nat
|
||||
# iptables -nL -t nat
|
||||
```
|
||||
|
||||
2.2 使用全局路由转发(二选一)
|
||||
|
||||
```shell
|
||||
# 假设anylink所在服务器的内网ip: 10.1.2.10
|
||||
|
||||
# 首先关闭nat转发功能
|
||||
iptables_nat = false
|
||||
|
||||
# 传统网络架构,在华三交换机添加以下静态路由规则
|
||||
ip route-static 192.168.90.0 255.255.255.0 10.1.2.10
|
||||
# 其他品牌的交换机命令,请参考以下地址
|
||||
https://cloud.tencent.com/document/product/216/62007
|
||||
|
||||
# 公有云环境下,需设置vpc下的路由表,添加以下路由策略
|
||||
目的端: 192.168.90.0/24
|
||||
下一跳类型: 云服务器
|
||||
下一跳: 10.1.2.10
|
||||
|
||||
```
|
||||
|
||||
3. 使用 AnyConnect 客户端连接即可
|
||||
@@ -146,62 +211,46 @@ iptables -nL -t nat
|
||||
> 以下参数可以通过执行 `ip a` 查看
|
||||
|
||||
```
|
||||
# 首先关闭nat转发功能
|
||||
iptables_nat = false
|
||||
|
||||
# master网卡需要打开混杂模式
|
||||
ip link set dev eth0 promisc on
|
||||
|
||||
#内网主网卡名称
|
||||
ipv4_master = "eth0"
|
||||
#以下网段需要跟ipv4_master网卡设置成一样
|
||||
ipv4_cidr = "192.168.10.0/24"
|
||||
ipv4_gateway = "192.168.10.1"
|
||||
ipv4_start = "192.168.10.100"
|
||||
ipv4_end = "192.168.10.200"
|
||||
```
|
||||
|
||||
### tap 设置
|
||||
|
||||
1. 创建桥接网卡
|
||||
|
||||
```
|
||||
注意 server.toml 的ip参数,需要与 bridge-init.sh 的配置参数一致
|
||||
```
|
||||
|
||||
2. 修改 bridge-init.sh 内的参数
|
||||
|
||||
> 以下参数可以通过执行 `ip a` 查看
|
||||
|
||||
```
|
||||
eth="eth0"
|
||||
eth_ip="192.168.10.4/24"
|
||||
eth_broadcast="192.168.10.255"
|
||||
eth_gateway="192.168.10.1"
|
||||
```
|
||||
|
||||
3. 执行 bridge-init.sh 文件
|
||||
|
||||
```
|
||||
sh bridge-init.sh
|
||||
ipv4_cidr = "10.1.2.0/24"
|
||||
ipv4_gateway = "10.1.2.1"
|
||||
ipv4_start = "10.1.2.100"
|
||||
ipv4_end = "10.1.2.200"
|
||||
```
|
||||
|
||||
## Systemd
|
||||
|
||||
添加 systemd 脚本
|
||||
1. 添加 anylink 程序
|
||||
|
||||
- anylink 程序目录放入 `/usr/local/anylink-deploy`
|
||||
- anylink 程序目录放入 `/usr/local/anylink-deploy`
|
||||
- 添加执行权限 `chmod +x /usr/local/anylink-deploy/anylink`
|
||||
|
||||
systemd 脚本放入:
|
||||
2. systemd/anylink.service 脚本放入:
|
||||
|
||||
- centos: `/usr/lib/systemd/system/`
|
||||
- ubuntu: `/lib/systemd/system/`
|
||||
- centos: `/usr/lib/systemd/system/`
|
||||
- ubuntu: `/lib/systemd/system/`
|
||||
|
||||
操作命令:
|
||||
3. 操作命令:
|
||||
|
||||
- 启动: `systemctl start anylink`
|
||||
- 停止: `systemctl stop anylink`
|
||||
- 开机自启: `systemctl enable anylink`
|
||||
- 启动: `systemctl start anylink`
|
||||
- 停止: `systemctl stop anylink`
|
||||
- 开机自启: `systemctl enable anylink`
|
||||
|
||||
## Docker
|
||||
|
||||
1. 获取镜像
|
||||
|
||||
```bash
|
||||
# 具体tag可以从docker hub获取
|
||||
# https://hub.docker.com/r/bjdgyc/anylink/tags
|
||||
docker pull bjdgyc/anylink:latest
|
||||
```
|
||||
|
||||
@@ -235,45 +284,41 @@ systemd 脚本放入:
|
||||
```
|
||||
|
||||
6. 使用自定义参数启动容器
|
||||
|
||||
```bash
|
||||
# 参数可以参考 -h 命令
|
||||
docker run -itd --name anylink --privileged \
|
||||
-e IPV4_CIDR=192.168.10.0/24 \
|
||||
-p 443:443 -p 8800:8800 \
|
||||
--restart=always \
|
||||
bjdgyc/anylink \
|
||||
-c=/etc/server.toml --ip_lease = 1209600 \ # IP地址租约时长
|
||||
-c=/etc/server.toml --ip_lease=1209600 # IP地址租约时长
|
||||
```
|
||||
|
||||
7. 构建镜像
|
||||
7. 构建镜像 (非必需)
|
||||
|
||||
```bash
|
||||
#获取仓库源码
|
||||
git clone https://github.com/bjdgyc/anylink.git
|
||||
# 构建镜像
|
||||
docker build -t anylink .
|
||||
docker build -t anylink -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
请前往 [问题地址](question.md) 查看具体信息
|
||||
|
||||
## Donate
|
||||
|
||||
> 如果您觉得 anylink 对你有帮助,欢迎给我们打赏,也是帮助 anylink 更好的发展。
|
||||
>
|
||||
> [查看打赏列表](doc/README.md)
|
||||
|
||||
<p>
|
||||
<img src="doc/screenshot/wxpay2.png" width="400" />
|
||||
</p>
|
||||
请前往 [问题地址](doc/question.md) 查看具体信息
|
||||
|
||||
## Discussion
|
||||
|
||||
添加 QQ 群: 567510628
|
||||
添加QQ群(1): 567510628
|
||||
|
||||
QQ 群共享文件有相关软件下载
|
||||
添加QQ群(2): 739072205
|
||||
|
||||
群共享文件有相关软件下载
|
||||
|
||||
<!--
|
||||
添加微信群: 群共享文件有相关软件下载
|
||||
|
||||

|
||||
-->
|
||||
|
||||
## Contribution
|
||||
|
||||
@@ -296,7 +341,7 @@ QQ 群共享文件有相关软件下载
|
||||
|
||||
## License
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
||||
本项目采用 AGPL-3.0 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
||||
|
||||
## Thank
|
||||
|
||||
|
21
build.sh
21
build.sh
@@ -1,4 +1,4 @@
|
||||
#!/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
function RETVAL() {
|
||||
@@ -12,13 +12,21 @@ function RETVAL() {
|
||||
#当前目录
|
||||
cpath=$(pwd)
|
||||
|
||||
ver=`cat server/base/app_ver.go | grep APP_VER | awk '{print $3}' | sed 's/"//g'`
|
||||
echo "当前版本 $ver"
|
||||
|
||||
echo "编译前端项目"
|
||||
cd $cpath/web
|
||||
#国内可替换源加快速度
|
||||
#npx browserslist@latest --update-db
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
#npm install --registry=https://registry.npm.taobao.org
|
||||
#npm install
|
||||
npm run build
|
||||
#npm run build
|
||||
|
||||
yarn install --registry=https://registry.npmmirror.com
|
||||
yarn run build
|
||||
|
||||
|
||||
RETVAL $?
|
||||
|
||||
echo "编译二进制文件"
|
||||
@@ -27,7 +35,8 @@ rm -rf ui
|
||||
cp -rf $cpath/web/ui .
|
||||
#国内可替换源加快速度
|
||||
export GOPROXY=https://goproxy.io
|
||||
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
|
||||
go mod tidy
|
||||
go build -v -o anylink -ldflags "-s -w -X main.CommitId=$(git rev-parse HEAD)"
|
||||
RETVAL $?
|
||||
|
||||
cd $cpath
|
||||
@@ -38,10 +47,12 @@ rm -rf $deploy ${deploy}.tar.gz
|
||||
mkdir $deploy
|
||||
|
||||
cp -r server/anylink $deploy
|
||||
cp -r server/bridge-init.sh $deploy
|
||||
#cp -r server/bridge-init.sh $deploy
|
||||
cp -r server/conf $deploy
|
||||
|
||||
cp -r systemd $deploy
|
||||
cp -r LICENSE $deploy
|
||||
cp -r home $deploy
|
||||
|
||||
tar zcvf ${deploy}.tar.gz $deploy
|
||||
|
||||
|
18
build_docker.sh
Normal file
18
build_docker.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
ver=`cat server/base/app_ver.go | grep APP_VER | awk '{print $3}' | sed 's/"//g'`
|
||||
echo $ver
|
||||
|
||||
#docker login -u bjdgyc
|
||||
|
||||
#docker build -t bjdgyc/anylink .
|
||||
|
||||
docker build -t bjdgyc/anylink --build-arg GitCommitId=$(git rev-parse HEAD) -f docker/Dockerfile .
|
||||
|
||||
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
|
||||
|
||||
exit 0
|
||||
|
||||
docker push bjdgyc/anylink:$ver
|
||||
docker push bjdgyc/anylink:latest
|
||||
|
@@ -3,15 +3,47 @@
|
||||
> 如果您觉得 AnyLink 对你有帮助,欢迎给我们打赏,也是帮助 AnyLink 更好的发展。
|
||||
|
||||
<p>
|
||||
<img src="screenshot/wxpay2.png" width="500" />
|
||||
<img src="screenshot/wxpay2.png" width="435" alt="anylink捐赠二维码" />
|
||||
</p>
|
||||
|
||||
## Donator
|
||||
|
||||
> 感谢以下同学的打赏,AnyLink 有你更美好!
|
||||
>
|
||||
> 需要展示主页的同学,可以在QQ群 直接联系我添加。
|
||||
|
||||
| 昵称 | 主页 / 留言 |
|
||||
|-----------|------------------------------|
|
||||
| 代码 oo8 | |
|
||||
| 甘磊 | https://github.com/ganlei333 |
|
||||
| Oo@ | https://github.com/chooop |
|
||||
| 虚极静笃 | |
|
||||
| 请喝可乐 | |
|
||||
| 加油加油 | |
|
||||
| 李建 | |
|
||||
| lanbin | |
|
||||
| 乐在东途 | |
|
||||
| 孤鸿 | |
|
||||
| 刘国华 | |
|
||||
| 改名好无聊 | |
|
||||
| 全能互联网专家 | |
|
||||
| JCM | |
|
||||
| Eh... | |
|
||||
| 沉 | |
|
||||
| 刘国华 | |
|
||||
| 忧郁的豚骨拉面 | |
|
||||
| 张小旋当爹地 | |
|
||||
| 对方正在输入 | |
|
||||
| Ronny | |
|
||||
| 奔跑的少年 | |
|
||||
| ZBW | |
|
||||
| 悲鸣 | |
|
||||
| 谢谢 | |
|
||||
| 云思科技 | |
|
||||
| 哆啦A伟(张佳伟) | 嘿嘿 |
|
||||
| 人类的悲欢并不相通 | 开源不易,感谢分享 |
|
||||
| 做人要低调 | |
|
||||
|
||||
|
||||
|
||||
|
||||
| 昵称 | 主页 |
|
||||
| -------- | ---------------------------- |
|
||||
| 代码oo8 | |
|
||||
| 甘磊 | https://github.com/ganlei333 |
|
||||
| Oo@ | https://github.com/chooop |
|
||||
|
@@ -1,34 +1,100 @@
|
||||
# 常见问题
|
||||
## 常见问题
|
||||
|
||||
### anyconnect 客户端问题
|
||||
|
||||
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
|
||||
>
|
||||
> 添加QQ群: 567510628
|
||||
|
||||
### OTP 动态码
|
||||
|
||||
> 请使用手机安装 freeotp ,然后扫描otp二维码,生成的数字即是动态码
|
||||
|
||||
### 远程桌面连接
|
||||
|
||||
> 本软件已经支持远程桌面里面连接anyconnect。
|
||||
|
||||
### 私有证书问题
|
||||
|
||||
> anylink 默认不支持私有证书
|
||||
>
|
||||
> 其他使用私有证书的问题,请自行解决
|
||||
|
||||
### 客户端连接名称
|
||||
|
||||
> 客户端连接名称需要修改 [profile.xml](../server/conf/profile.xml) 文件
|
||||
|
||||
```xml
|
||||
|
||||
<HostEntry>
|
||||
<HostName>VPN</HostName>
|
||||
<HostAddress>localhost</HostAddress>
|
||||
</HostEntry>
|
||||
```
|
||||
|
||||
### dpd timeout 设置问题
|
||||
```
|
||||
|
||||
```yaml
|
||||
#客户端失效检测时间(秒) dpd > keepalive
|
||||
cstp_keepalive = 20
|
||||
cstp_dpd = 30
|
||||
mobile_keepalive = 40
|
||||
mobile_dpd = 50
|
||||
cstp_keepalive = 4
|
||||
cstp_dpd = 9
|
||||
mobile_keepalive = 7
|
||||
mobile_dpd = 15
|
||||
```
|
||||
|
||||
> 以上dpd参数为客户端的超时检测时间, 如一段时间内,没有数据传输,防火墙会主动关闭连接
|
||||
>
|
||||
> 如经常出现 timeout 的错误信息,应根据当前防火墙的设置,适当减小dpd数值
|
||||
|
||||
### 反向代理问题
|
||||
|
||||
> anylink 仅支持四层反向代理,不支持七层反向代理
|
||||
>
|
||||
> 如Nginx请使用 stream模块
|
||||
|
||||
```conf
|
||||
stream {
|
||||
upstream anylink_server {
|
||||
server 127.0.0.1:8443;
|
||||
}
|
||||
server {
|
||||
listen 443 tcp;
|
||||
proxy_timeout 30s;
|
||||
proxy_pass anylink_server;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> nginx实现 共用443端口 示例
|
||||
|
||||
```conf
|
||||
stream {
|
||||
map $ssl_preread_server_name $name {
|
||||
vpn.xx.com myvpn;
|
||||
default defaultpage;
|
||||
}
|
||||
|
||||
# upstream pool
|
||||
upstream myvpn {
|
||||
server 127.0.0.1:8443;
|
||||
}
|
||||
upstream defaultpage {
|
||||
server 127.0.0.1:8080;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 so_keepalive=on;
|
||||
ssl_preread on;
|
||||
#接收端也需要设置 proxy_protocol
|
||||
#proxy_protocol on;
|
||||
proxy_pass $name;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 性能问题
|
||||
|
||||
```
|
||||
内网环境测试数据
|
||||
虚拟服务器: centos7 4C8G
|
||||
@@ -37,6 +103,7 @@ anylink: tun模式 tcp传输
|
||||
客户端网卡下载速度:270Mb/s
|
||||
服务端网卡上传速度:280Mb/s
|
||||
```
|
||||
|
||||
> 客户端tls加密协议、隧道header头都会占用一定带宽
|
||||
|
||||
|
||||
|
@@ -1,6 +1,58 @@
|
||||
FROM ubuntu:18.04
|
||||
WORKDIR /
|
||||
COPY docker_entrypoint.sh docker_entrypoint.sh
|
||||
RUN mkdir /anylink && apt update && apt install -y wget iptables tar iproute2
|
||||
ENTRYPOINT ["/docker_entrypoint.sh"]
|
||||
#CMD ["/anylink/anylink","-conf=/anylink/conf/server.toml"]
|
||||
#node:16-bullseye
|
||||
#golang:1.20-bullseye
|
||||
#debian:bullseye-slim
|
||||
#sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
|
||||
|
||||
|
||||
# web
|
||||
FROM node:16-alpine3.18 as builder_node
|
||||
WORKDIR /web
|
||||
COPY ./web /web
|
||||
RUN yarn install \
|
||||
&& yarn run build \
|
||||
&& ls /web/ui
|
||||
|
||||
|
||||
# server
|
||||
FROM golang:1.20-alpine3.18 as builder_golang
|
||||
#TODO 本地打包时使用镜像
|
||||
ENV GOPROXY=https://goproxy.cn
|
||||
ENV GOOS=linux
|
||||
ARG GitCommitId="gitCommitId"
|
||||
|
||||
WORKDIR /anylink
|
||||
COPY server /anylink
|
||||
COPY --from=builder_node /web/ui /anylink/ui
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add gcc musl-dev
|
||||
RUN cd /anylink;go mod tidy;go build -o anylink -ldflags "-s -w -X main.CommitId=${GitCommitId}" \
|
||||
&& /anylink/anylink tool -v
|
||||
|
||||
|
||||
# anylink
|
||||
FROM alpine:3.18
|
||||
LABEL maintainer="github.com/bjdgyc"
|
||||
|
||||
ENV ANYLINK_IN_CONTAINER=true
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder_golang /anylink/anylink /app/
|
||||
COPY docker/docker_entrypoint.sh /app/
|
||||
COPY ./server/bridge-init.sh /app/
|
||||
COPY ./server/conf /app/conf
|
||||
COPY ./LICENSE /app/LICENSE
|
||||
COPY ./home /app/home
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache bash iptables \
|
||||
&& chmod +x /app/docker_entrypoint.sh \
|
||||
&& ls /app
|
||||
|
||||
EXPOSE 443 8800
|
||||
|
||||
#CMD ["/app/anylink"]
|
||||
ENTRYPOINT ["/app/docker_entrypoint.sh"]
|
||||
|
||||
|
@@ -1,41 +1,23 @@
|
||||
#!/bin/sh
|
||||
USER="admin"
|
||||
MM=$(pwgen -1s)
|
||||
CREATE_USER=1
|
||||
CONFIG_FILE='/app/conf/server.toml'
|
||||
var1=$1
|
||||
|
||||
if [ $CREATE_USER -eq 1 ]; then
|
||||
if [ ! -e $CREATE_USER ]; then
|
||||
MM=$(pwgen -1s)
|
||||
touch $CREATE_USER
|
||||
bash /app/generate-certs.sh
|
||||
cd /app/conf/ && cp *.crt /usr/local/share/ca-certificates/
|
||||
update-ca-certificates --fresh
|
||||
userpass=$(/app/anylink -passwd "${MM}"| cut -d : -f2)
|
||||
echo "${userpass}"
|
||||
jwttoken=$(/app/anylink -secret | cut -d : -f2)
|
||||
echo "-- First container startup --user:${USER} pwd:${MM}"
|
||||
sed -i "s/admin/${USER}/g" /app/server-example.toml
|
||||
sed -i "s/123456/${MM}/g" /app/server-example.toml
|
||||
sed -i "s#usertoken#${userpass}#g" /app/server-example.toml
|
||||
sed -i "s/jwttoken/${jwttoken}/g" /app/server-example.toml
|
||||
else
|
||||
echo "-- Not first container startup --"
|
||||
fi
|
||||
#set -x
|
||||
|
||||
else
|
||||
echo "user switch not create"
|
||||
case $var1 in
|
||||
"bash" | "sh")
|
||||
echo $var1
|
||||
exec "$@"
|
||||
;;
|
||||
|
||||
fi
|
||||
"tool")
|
||||
/app/anylink "$@"
|
||||
;;
|
||||
|
||||
if [ ! -f $CONFIG_FILE ]; then
|
||||
echo "#####Generating configuration file#####"
|
||||
cp /app/server-example.toml /app/conf/server.toml
|
||||
else
|
||||
echo "#####Configuration file already exists#####"
|
||||
fi
|
||||
*)
|
||||
#sysctl -w net.ipv4.ip_forward=1
|
||||
#iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
|
||||
#iptables -nL -t nat
|
||||
|
||||
rtaddr=$(grep "cidr" /app/conf/server.toml |awk -F \" '{print $2}')
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -s "${rtaddr}" -o eth0+ -j MASQUERADE
|
||||
/app/anylink -conf="/app/conf/server.toml"
|
||||
exec /app/anylink "$@"
|
||||
;;
|
||||
esac
|
||||
|
@@ -1,37 +0,0 @@
|
||||
#! /bin/bash
|
||||
version=(`wget -qO- -t1 -T2 "https://api.github.com/repos/bjdgyc/anylink/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g'`)
|
||||
count=(`ls anylink | wc -w `)
|
||||
wget https://github.com/bjdgyc/anylink/releases/download/${version}/anylink-deploy.tar.gz
|
||||
tar xf anylink-deploy.tar.gz
|
||||
rm -rf anylink-deploy.tar.gz
|
||||
if [ ${count} -eq 0 ]; then
|
||||
echo "init anylink"
|
||||
mv anylink-deploy/* anylink/
|
||||
else
|
||||
if [ ! -d "/anylink/log" ]; then
|
||||
mv anylink-deploy/log anylink/
|
||||
fi
|
||||
if [ ! -d "/anylink/conf" ]; then
|
||||
mv anylink-deploy/conf anylink/
|
||||
fi
|
||||
echo "update anylink"
|
||||
rm -rf anylink/ui anylink/anylink anylink/files
|
||||
mv anylink-deploy/ui anylink/
|
||||
mv anylink-deploy/anylink anylink/
|
||||
mv anylink-deploy/files anylink/
|
||||
fi
|
||||
rm -rf anylink-deploy
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
if [[ ${mode} == pro ]];then
|
||||
iptables -t nat -A POSTROUTING -s ${iproute} -o eth0 -j MASQUERADE
|
||||
iptables -L -n -t nat
|
||||
/anylink/anylink -conf=/anylink/conf/server.toml
|
||||
elif [[ ${mode} == password ]];then
|
||||
if [ -z ${password} ];then
|
||||
echo "invalid password"
|
||||
else
|
||||
/anylink/anylink -passwd ${password}
|
||||
fi
|
||||
elif [[ ${mode} -eq jwt ]];then
|
||||
/anylink/anylink -secret
|
||||
fi
|
@@ -1,13 +0,0 @@
|
||||
#!/bin/env bash
|
||||
|
||||
ver="0.5.1"
|
||||
|
||||
#docker login -u bjdgyc
|
||||
|
||||
docker build -t bjdgyc/anylink .
|
||||
|
||||
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
|
||||
|
||||
docker push bjdgyc/anylink:$ver
|
||||
docker push bjdgyc/anylink:latest
|
||||
|
@@ -1,23 +0,0 @@
|
||||
#!/bin/sh
|
||||
var1=$1
|
||||
|
||||
#set -x
|
||||
|
||||
case $var1 in
|
||||
"bash" | "sh")
|
||||
echo $var1
|
||||
exec "$@"
|
||||
;;
|
||||
|
||||
"tool")
|
||||
/app/anylink "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
|
||||
iptables -nL -t nat
|
||||
|
||||
/app/anylink "$@"
|
||||
;;
|
||||
esac
|
@@ -1,21 +0,0 @@
|
||||
# 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
|
61
dtls-2.0.9/.github/assert-contributors.sh
vendored
61
dtls-2.0.9/.github/assert-contributors.sh
vendored
@@ -1,61 +0,0 @@
|
||||
#!/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
|
11
dtls-2.0.9/.github/hooks/commit-msg.sh
vendored
11
dtls-2.0.9/.github/hooks/commit-msg.sh
vendored
@@ -1,11 +0,0 @@
|
||||
#!/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
|
12
dtls-2.0.9/.github/hooks/pre-commit.sh
vendored
12
dtls-2.0.9/.github/hooks/pre-commit.sh
vendored
@@ -1,12 +0,0 @@
|
||||
#!/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
|
13
dtls-2.0.9/.github/hooks/pre-push.sh
vendored
13
dtls-2.0.9/.github/hooks/pre-push.sh
vendored
@@ -1,13 +0,0 @@
|
||||
#!/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
|
16
dtls-2.0.9/.github/install-hooks.sh
vendored
16
dtls-2.0.9/.github/install-hooks.sh
vendored
@@ -1,16 +0,0 @@
|
||||
#!/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"
|
64
dtls-2.0.9/.github/lint-commit-message.sh
vendored
64
dtls-2.0.9/.github/lint-commit-message.sh
vendored
@@ -1,64 +0,0 @@
|
||||
#!/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
|
@@ -1,48 +0,0 @@
|
||||
#!/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
|
24
dtls-2.0.9/.github/lint-filename.sh
vendored
24
dtls-2.0.9/.github/lint-filename.sh
vendored
@@ -1,24 +0,0 @@
|
||||
#!/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
|
20
dtls-2.0.9/.github/workflows/e2e.yaml
vendored
20
dtls-2.0.9/.github/workflows/e2e.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
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
|
43
dtls-2.0.9/.github/workflows/lint.yaml
vendored
43
dtls-2.0.9/.github/workflows/lint.yaml
vendored
@@ -1,43 +0,0 @@
|
||||
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
|
@@ -1,33 +0,0 @@
|
||||
#
|
||||
# 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
|
139
dtls-2.0.9/.github/workflows/test.yaml
vendored
139
dtls-2.0.9/.github/workflows/test.yaml
vendored
@@ -1,139 +0,0 @@
|
||||
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
|
37
dtls-2.0.9/.github/workflows/tidy-check.yaml
vendored
37
dtls-2.0.9/.github/workflows/tidy-check.yaml
vendored
@@ -1,37 +0,0 @@
|
||||
#
|
||||
# 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
|
24
dtls-2.0.9/.gitignore
vendored
24
dtls-2.0.9/.gitignore
vendored
@@ -1,24 +0,0 @@
|
||||
### 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
|
@@ -1,89 +0,0 @@
|
||||
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
|
@@ -1,21 +0,0 @@
|
||||
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.
|
@@ -1,6 +0,0 @@
|
||||
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
|
@@ -1,156 +0,0 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
Pion DTLS
|
||||
<br>
|
||||
</h1>
|
||||
<h4 align="center">A Go implementation of DTLS</h4>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-dtls-gray.svg?longCache=true&colorB=brightgreen" alt="Pion DTLS"></a>
|
||||
<a href="https://sourcegraph.com/github.com/pion/dtls"><img src="https://sourcegraph.com/github.com/pion/dtls/-/badge.svg" alt="Sourcegraph Widget"></a>
|
||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<br>
|
||||
<a href="https://travis-ci.org/pion/dtls"><img src="https://travis-ci.org/pion/dtls.svg?branch=master" alt="Build Status"></a>
|
||||
<a href="https://pkg.go.dev/github.com/pion/dtls"><img src="https://godoc.org/github.com/pion/dtls?status.svg" alt="GoDoc"></a>
|
||||
<a href="https://codecov.io/gh/pion/dtls"><img src="https://codecov.io/gh/pion/dtls/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/pion/dtls"><img src="https://goreportcard.com/badge/github.com/pion/dtls" alt="Go Report Card"></a>
|
||||
<a href="https://www.codacy.com/app/Sean-Der/dtls"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
Native [DTLS 1.2][rfc6347] implementation in the Go programming language.
|
||||
|
||||
A long term goal is a professional security review, and 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
|
@@ -1,118 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,213 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// +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
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
// +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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
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}}
|
||||
})
|
||||
})
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
#
|
||||
# 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/**/*"
|
@@ -1,9 +0,0 @@
|
||||
package dtls
|
||||
|
||||
import "github.com/pion/dtls/v2/pkg/protocol"
|
||||
|
||||
func defaultCompressionMethods() []*protocol.CompressionMethod {
|
||||
return []*protocol.CompressionMethod{
|
||||
{},
|
||||
}
|
||||
}
|
@@ -1,197 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -1,979 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
// +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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,221 +0,0 @@
|
||||
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)
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
// Package dtls implements Datagram Transport Layer Security (DTLS) 1.2
|
||||
package dtls
|
@@ -1,11 +0,0 @@
|
||||
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", "."]
|
@@ -1,2 +0,0 @@
|
||||
// Package e2e contains end to end tests for pion/dtls
|
||||
package e2e
|
@@ -1,207 +0,0 @@
|
||||
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):
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,250 +0,0 @@
|
||||
// +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)
|
||||
})
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
// +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)
|
||||
})
|
||||
}
|
@@ -1,329 +0,0 @@
|
||||
// +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)
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
// +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)
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
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}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
// +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
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
// +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)
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
// +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
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
# 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"
|
||||
```
|
@@ -1,5 +0,0 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIGOO78dEAcepxdUIeDzC28jMcFrJr2q7x+UdhgtJ/RS3oAoGCCqGSM49
|
||||
AwEHoUQDQgAEGLSNxlkJ9mETKI2Hogq3Cyh06pJKA1YMgcKqYKS6yQQlvvk5rU88
|
||||
+RojFPgXJukymhfIJmw4eGxxEMSjuEZY7w==
|
||||
-----END EC PRIVATE KEY-----
|
@@ -1,9 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBLTCB1aADAgECAgMAq80wCgYIKoZIzj0EAwIwDTELMAkGA1UEBhMCTkwwHhcN
|
||||
MjAwMzIwMDk0NjQ0WhcNMjEwMzIwMDk0NjQ0WjANMQswCQYDVQQGEwJOTDBZMBMG
|
||||
ByqGSM49AgEGCCqGSM49AwEHA0IABBi0jcZZCfZhEyiNh6IKtwsodOqSSgNWDIHC
|
||||
qmCkuskEJb75Oa1PPPkaIxT4FybpMpoXyCZsOHhscRDEo7hGWO+jJDAiMA8GA1Ud
|
||||
EQQIMAaHBH8AAAEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBx
|
||||
sIkcADN9E60veZOFOeANaRWAiQaLWZfUxqkOmfHztQIgI2CfHMjDQwJZFh35HvFs
|
||||
NOPJj8wxFhqR5pqMF23cgOY=
|
||||
-----END CERTIFICATE-----
|
@@ -1,5 +0,0 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIDT8Xyx5RpPP+98ulYZKsvKIVdBUJug/L9H2M8JThv+GoAoGCCqGSM49
|
||||
AwEHoUQDQgAE6Wf0qQqIb5G7g51P83Dh1Yst52kyntGYz1Bt6S7crpmQFs9ZRZMy
|
||||
bJ6MGIwGcVBMgoL3pfxDKdZ3mnzmoibU0w==
|
||||
-----END EC PRIVATE KEY-----
|
@@ -1,9 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBPzCB5qADAgECAhRtzyVTL+9D0KHfbcKYeKckpLVRmTAKBggqhkjOPQQDAjAN
|
||||
MQswCQYDVQQGEwJOTDAeFw0yMDAzMjAwOTQ2NDRaFw0yMTAzMjAwOTQ2NDRaMA0x
|
||||
CzAJBgNVBAYTAk5MMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Wf0qQqIb5G7
|
||||
g51P83Dh1Yst52kyntGYz1Bt6S7crpmQFs9ZRZMybJ6MGIwGcVBMgoL3pfxDKdZ3
|
||||
mnzmoibU06MkMCIwDwYDVR0RBAgwBocEfwAAATAPBgNVHRMBAf8EBTADAQH/MAoG
|
||||
CCqGSM49BAMCA0gAMEUCIQD000SU+klkNLGvHZcMYNVkCFsImnGKIqPMy3LELSiF
|
||||
0gIgSGIFkNEIAyNxn44CXZJu3piyz1ouK2fLefDJMYfcXgM=
|
||||
-----END CERTIFICATE-----
|
@@ -1,45 +0,0 @@
|
||||
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)
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
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)
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
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)
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
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()
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
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()
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
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()
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
@@ -1,154 +0,0 @@
|
||||
// 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
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,102 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,194 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,352 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,323 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
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
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
// +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
|
||||
}
|
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<14>12<08><><EFBFBD><EFBFBD>[A51
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user