Compare commits
71 Commits
Author | SHA1 | Date |
---|---|---|
Frank Denis | 8dadd61730 | |
dependabot[bot] | f7da81cf29 | |
Frank Denis | 0efce55895 | |
Frank Denis | 5a1d94d506 | |
Frank Denis | 271943c158 | |
Frank Denis | 34a1f2ebf5 | |
Frank Denis | f8ce22d9b9 | |
Frank Denis | 249dba391d | |
Frank Denis | 987ae216e3 | |
Frank Denis | 7fba32651b | |
Frank Denis | 6ae388e646 | |
Frank Denis | 0af88bc875 | |
Frank Denis | d36edeb612 | |
Frank Denis | 041a6c7d7f | |
cuibuwei | 2c6416d5ae | |
Frank Denis | 4d1cd67d4d | |
Frank Denis | 363d44919f | |
Frank Denis | a88076d06f | |
Frank Denis | 119bc0b660 | |
Robert Edmonds | 49000cd4f4 | |
Frank Denis | ec46e09360 | |
dependabot[bot] | ea5808e024 | |
Frank Denis | 79a1aa8325 | |
Alison Winters | 4c442f5dbb | |
Alison Winters | f7e13502c0 | |
YX Hao | 8d43ce9b56 | |
YX Hao | ac5087315c | |
Frank Denis | ad80d81d43 | |
dependabot[bot] | a7fb13ba4e | |
Frank Denis | 006619837f | |
Frank Denis | 093936f7ab | |
Frank Denis | 7462961980 | |
Frank Denis | 0b559bb54f | |
Frank Denis | 658835b4ff | |
Frank Denis | 90c3017793 | |
dependabot[bot] | e371138b86 | |
Frank Denis | bcbf2db4ff | |
YX Hao | 64fa90839c | |
Frank Denis | f2484f5bd5 | |
Frank Denis | 63f8d9b30d | |
Xiaotong Liu | 49e3570c2c | |
lifenjoiner | 3be53642fe | |
YX Hao | 13e7077200 | |
Frank Denis | f5912d7ca9 | |
dependabot[bot] | 0196d7d2ab | |
Frank Denis | 898ded9c52 | |
Frank Denis | e782207911 | |
Frank Denis | 0f1f635ec1 | |
keatonLiu | 956a14ee21 | |
dependabot[bot] | 22731786a2 | |
Frank Denis | 42b6ae9052 | |
YX Hao | 0d5e52bb16 | |
Frank Denis | 0ba728b6ce | |
Frank Denis | cb80bf33e8 | |
Frank Denis | 88207560a7 | |
Jeffrey Damick | 4a361dbb05 | |
Frank Denis | b37a5c991a | |
Frank Denis | 0232870870 | |
Frank Denis | 1a9bf8a286 | |
Frank Denis | 7fb58720fb | |
Frank Denis | f85b3e81ec | |
Frank Denis | 79779cf744 | |
Frank Denis | 8bea679e7b | |
Frank Denis | 96f21f1bff | |
dependabot[bot] | 21097686c1 | |
Frank Denis | 87571d4a7f | |
Frank Denis | f531c8fffb | |
Frank Denis | 5ae83c1592 | |
Frank Denis | c86e9a90cc | |
Frank Denis | d48c811ea9 | |
Frank Denis | f2b1edcec2 |
|
@ -0,0 +1,58 @@
|
|||
#! /bin/sh
|
||||
|
||||
PACKAGE_VERSION="$1"
|
||||
|
||||
cd dnscrypt-proxy || exit 1
|
||||
|
||||
# setup the environment
|
||||
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y wget wine dotnet-sdk-6.0
|
||||
sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y wine32
|
||||
|
||||
sudo apt-get install -y unzip
|
||||
|
||||
export WINEPREFIX="$HOME"/.wine32
|
||||
export WINEARCH=win32
|
||||
export WINEDEBUG=-all
|
||||
|
||||
wget https://dl.winehq.org/wine/wine-mono/8.1.0/wine-mono-8.1.0-x86.msi
|
||||
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wineboot --init
|
||||
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wine msiexec /i wine-mono-8.1.0-x86.msi
|
||||
|
||||
mkdir "$HOME"/.wine32/drive_c/temp
|
||||
mkdir -p "$HOME"/.wine/drive_c/temp
|
||||
wget https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip -nv -O wix.zip
|
||||
|
||||
unzip wix.zip -d "$HOME"/wix
|
||||
rm -f wix.zip
|
||||
|
||||
builddir=$(pwd)
|
||||
srcdir=$(
|
||||
cd ..
|
||||
pwd
|
||||
)
|
||||
version=$PACKAGE_VERSION
|
||||
|
||||
cd "$HOME"/wix || exit
|
||||
|
||||
ln -s "$builddir" "$HOME"/wix/build
|
||||
ln -s "$srcdir"/contrib/msi "$HOME"/wix/wixproj
|
||||
echo "builddir: $builddir"
|
||||
|
||||
# build the msi's
|
||||
#################
|
||||
for arch in x64 x86; do
|
||||
binpath="win32"
|
||||
if [ "$arch" = "x64" ]; then
|
||||
binpath="win64"
|
||||
fi
|
||||
|
||||
echo $arch
|
||||
|
||||
wine candle.exe -dVersion="$version" -dPlatform=$arch -dPath=build\\$binpath -arch $arch wixproj\\dnscrypt.wxs -out build\\dnscrypt-$arch.wixobj
|
||||
wine light.exe -out build\\dnscrypt-proxy-$arch-"$version".msi build\\dnscrypt-$arch.wixobj -sval
|
||||
|
||||
done
|
||||
|
||||
cd "$builddir" || exit
|
|
@ -66,11 +66,11 @@ t || dig -p${DNS_PORT} +dnssec darpa.mil @127.0.0.1 2>&1 | grep -Fvq 'RRSIG' ||
|
|||
t || dig -p${DNS_PORT} +dnssec www.darpa.mil @127.0.0.1 2>&1 | grep -Fvq 'RRSIG' || fail
|
||||
|
||||
section
|
||||
t || dig -p${DNS_PORT} +short cloaked.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
|
||||
t || dig -p${DNS_PORT} +short MX cloaked.com @127.0.0.1 | grep -Fq 'locally blocked' || fail
|
||||
t || dig -p${DNS_PORT} +short cloakedunregistered.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
|
||||
t || dig -p${DNS_PORT} +short MX cloakedunregistered.com @127.0.0.1 | grep -Fq 'locally blocked' || fail
|
||||
t || dig -p${DNS_PORT} +short MX example.com @127.0.0.1 | grep -Fvq 'locally blocked' || fail
|
||||
t || dig -p${DNS_PORT} NS cloaked.com @127.0.0.1 | grep -Fiq 'gtld-servers.net' || fail
|
||||
t || dig -p${DNS_PORT} +short www.cloaked2.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
|
||||
t || dig -p${DNS_PORT} NS cloakedunregistered.com @127.0.0.1 | grep -Fiq 'gtld-servers.net' || fail
|
||||
t || dig -p${DNS_PORT} +short www.cloakedunregistered2.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
|
||||
t || dig -p${DNS_PORT} +short www.dnscrypt-test @127.0.0.1 | grep -Fq '192.168.100.100' || fail
|
||||
t || dig -p${DNS_PORT} a.www.dnscrypt-test @127.0.0.1 | grep -Fq 'NXDOMAIN' || fail
|
||||
t || dig -p${DNS_PORT} +short ptr 101.100.168.192.in-addr.arpa. @127.0.0.1 | grep -Eq 'www.dnscrypt-test.com' || fail
|
||||
|
@ -122,8 +122,8 @@ t || grep -Eq 'invalid.*SYNTH' query.log || fail
|
|||
t || grep -Eq '168.192.in-addr.arpa.*SYNTH' query.log || fail
|
||||
t || grep -Eq 'darpa.mil.*FORWARD' query.log || fail
|
||||
t || grep -Eq 'www.darpa.mil.*FORWARD' query.log || fail
|
||||
t || grep -Eq 'cloaked.com.*CLOAK' query.log || fail
|
||||
t || grep -Eq 'www.cloaked2.com.*CLOAK' query.log || fail
|
||||
t || grep -Eq 'cloakedunregistered.com.*CLOAK' query.log || fail
|
||||
t || grep -Eq 'www.cloakedunregistered2.com.*CLOAK' query.log || fail
|
||||
t || grep -Eq 'www.dnscrypt-test.*CLOAK' query.log || fail
|
||||
t || grep -Eq 'a.www.dnscrypt-test.*NXDOMAIN' query.log || fail
|
||||
t || grep -Eq 'telemetry.example.*REJECT' query.log || fail
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
cloaked.* one.one.one.one
|
||||
*.cloaked2.* one.one.one.one # inline comment
|
||||
=www.dnscrypt-test 192.168.100.100
|
||||
=www.dnscrypt-test.com 192.168.100.101
|
||||
=ipv6.dnscrypt-test.com fd02::1
|
||||
cloakedunregistered.* one.one.one.one
|
||||
*.cloakedunregistered2.* one.one.one.one # inline comment
|
||||
=www.dnscrypt-test 192.168.100.100
|
||||
=www.dnscrypt-test.com 192.168.100.101
|
||||
=ipv6.dnscrypt-test.com fd02::1
|
||||
|
|
|
@ -9,7 +9,7 @@ file = 'query.log'
|
|||
stamp = 'sdns://BQcAAAAAAAAADm9kb2guY3J5cHRvLnN4Ci9kbnMtcXVlcnk'
|
||||
|
||||
[static.'odohrelay']
|
||||
stamp = 'sdns://hQcAAAAAAAAAACCi3jNJDEdtNW4tvHN8J3lpIklSa2Wrj7qaNCgEgci9_BpvZG9oLXJlbGF5LmVkZ2Vjb21wdXRlLmFwcAEv'
|
||||
stamp = 'sdns://hQcAAAAAAAAADDg5LjM4LjEzMS4zOAAYb2RvaC1ubC5hbGVrYmVyZy5uZXQ6NDQzBi9wcm94eQ'
|
||||
|
||||
[anonymized_dns]
|
||||
routes = [
|
||||
|
|
|
@ -13,9 +13,7 @@ cache = true
|
|||
[query_log]
|
||||
file = 'query.log'
|
||||
|
||||
|
||||
|
||||
[static]
|
||||
|
||||
[static.'myserver']
|
||||
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'
|
||||
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'
|
||||
|
|
|
@ -13,15 +13,20 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
|
|
@ -28,10 +28,10 @@ jobs:
|
|||
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1
|
||||
check-latest: true
|
||||
|
@ -49,6 +49,11 @@ jobs:
|
|||
run: |
|
||||
.ci/ci-build.sh "${{ steps.get_version.outputs.VERSION }}"
|
||||
|
||||
- name: Package
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
.ci/ci-package.sh "${{ steps.get_version.outputs.VERSION }}"
|
||||
|
||||
- name: Install minisign and sign
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
|
@ -78,7 +83,7 @@ jobs:
|
|||
prerelease: false
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@c9b46fe7aad9f02afd89b12450b780f52dacfb2d
|
||||
uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -87,3 +92,4 @@ jobs:
|
|||
dnscrypt-proxy/*.zip
|
||||
dnscrypt-proxy/*.tar.gz
|
||||
dnscrypt-proxy/*.minisig
|
||||
dnscrypt-proxy/*.msi
|
||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
Scan-Build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Perform ShiftLeft Scan
|
||||
uses: ShiftLeftSecurity/scan-action@master
|
||||
|
@ -18,6 +18,6 @@ jobs:
|
|||
output: reports
|
||||
|
||||
- name: Upload report
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: reports
|
||||
|
|
|
@ -14,3 +14,6 @@ dnscrypt-proxy/dnscrypt-proxy
|
|||
.ci/*.md
|
||||
.ci/*.md.minisig
|
||||
.ci/test-dnscrypt-proxy.toml
|
||||
contrib/msi/*.msi
|
||||
contrib/msi/*.wixpdb
|
||||
contrib/msi/*.wixobj
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
FROM ubuntu:latest
|
||||
MAINTAINER dnscrypt-authors
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y wget wine dotnet-sdk-6.0 && \
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y wine32
|
||||
|
||||
|
||||
ENV WINEPREFIX=/root/.wine32 WINEARCH=win32 WINEDEBUG=-all
|
||||
|
||||
RUN wget https://dl.winehq.org/wine/wine-mono/8.1.0/wine-mono-8.1.0-x86.msi && \
|
||||
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wineboot --init && \
|
||||
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wine msiexec /i wine-mono-8.1.0-x86.msi && \
|
||||
mkdir $WINEPREFIX/drive_c/temp && \
|
||||
apt-get install -y unzip && \
|
||||
wget https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip -nv -O wix.zip && \
|
||||
unzip wix.zip -d /wix && \
|
||||
rm -f wix.zip
|
||||
|
||||
WORKDIR /wix
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Scripts and utilities related to building an .msi (Microsoft Standard Installer) file.
|
||||
|
||||
## Docker test image for building an MSI locally
|
||||
|
||||
```sh
|
||||
docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
|
||||
```
|
||||
|
||||
## Test building msi files for intel win32 & win64
|
||||
|
||||
```sh
|
||||
./build.sh
|
||||
```
|
|
@ -0,0 +1,30 @@
|
|||
#! /bin/sh
|
||||
|
||||
version=0.0.0
|
||||
gitver=$(git describe --tags --always --match="[0-9]*.[0-9]*.[0-9]*" --exclude='*[^0-9.]*')
|
||||
if [ "$gitver" != "" ]; then
|
||||
version=$gitver
|
||||
fi
|
||||
|
||||
# build the image by running: docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
|
||||
if [ "$(docker image list -q ubuntu:dnscrypt-msi)" = "" ]; then
|
||||
docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
|
||||
fi
|
||||
|
||||
image=ubuntu:dnscrypt-msi
|
||||
|
||||
for arch in x64 x86; do
|
||||
binpath="win32"
|
||||
if [ "$arch" = "x64" ]; then
|
||||
binpath="win64"
|
||||
fi
|
||||
src=$(
|
||||
cd ../../dnscrypt-proxy/$binpath || exit
|
||||
pwd
|
||||
)
|
||||
echo "$src"
|
||||
|
||||
docker run --rm -v "$(pwd)":/wixproj -v "$src":/src $image wine candle.exe -dVersion="$version" -dPlatform=$arch -dPath=\\src -arch $arch \\wixproj\\dnscrypt.wxs -out \\wixproj\\dnscrypt-$arch.wixobj
|
||||
docker run --rm -v "$(pwd)":/wixproj -v "$src":/src $image wine light.exe -out \\wixproj\\dnscrypt-proxy-$arch-"$version".msi \\wixproj\\dnscrypt-$arch.wixobj -sval
|
||||
|
||||
done
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<?if $(var.Platform)="x64" ?>
|
||||
<?define Program_Files="ProgramFiles64Folder"?>
|
||||
<?else ?>
|
||||
<?define Program_Files="ProgramFilesFolder"?>
|
||||
<?endif ?>
|
||||
<?ifndef var.Version?>
|
||||
<?error Undefined Version variable?>
|
||||
<?endif ?>
|
||||
<?ifndef var.Path?>
|
||||
<?error Undefined Path variable?>
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*"
|
||||
UpgradeCode="fbf99dd8-c21e-4f9b-a632-de53bb64c45e"
|
||||
Name="dnscrypt-proxy"
|
||||
Version="$(var.Version)"
|
||||
Manufacturer="DNSCrypt"
|
||||
Language="1033">
|
||||
|
||||
<Package InstallerVersion="200" Compressed="yes" Comments="Windows Installer Package" InstallScope="perMachine" />
|
||||
<Media Id="1" Cabinet="product.cab" EmbedCab="yes" />
|
||||
<MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />
|
||||
|
||||
<Upgrade Id="fbf99dd8-c21e-4f9b-a632-de53bb64c45e">
|
||||
<UpgradeVersion Minimum="$(var.Version)" OnlyDetect="yes" Property="NEWERVERSIONDETECTED" />
|
||||
<UpgradeVersion Minimum="2.1.0" Maximum="$(var.Version)" IncludeMinimum="yes" IncludeMaximum="no" Property="OLDERVERSIONBEINGUPGRADED" />
|
||||
</Upgrade>
|
||||
<Condition Message="A newer version of this software is already installed.">NOT NEWERVERSIONDETECTED</Condition>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="$(var.Program_Files)">
|
||||
<Directory Id="INSTALLDIR" Name="DNSCrypt">
|
||||
<Component Id="ApplicationFiles" Guid="7d693c0b-71d8-436a-9c84-60a11dc74092">
|
||||
<File Id="dnscryptproxy.exe" KeyPath="yes" Source="$(var.Path)\dnscrypt-proxy.exe" DiskId="1"/>
|
||||
<File Source="$(var.Path)\LICENSE"></File>
|
||||
<File Source="$(var.Path)\service-install.bat"></File>
|
||||
<File Source="$(var.Path)\service-restart.bat"></File>
|
||||
<File Source="$(var.Path)\service-uninstall.bat"></File>
|
||||
<File Source="$(var.Path)\example-dnscrypt-proxy.toml"></File>
|
||||
</Component>
|
||||
<Component Id="ConfigInstall" Guid="db7b691e-f7c7-4c9a-92e1-c6f21ce6430f" KeyPath="yes">
|
||||
<Condition><![CDATA[CONFIGFILE]]></Condition>
|
||||
<CopyFile Id="dnscryptproxytoml" DestinationDirectory="INSTALLDIR" DestinationName="dnscrypt-proxy.toml" SourceProperty="CONFIGFILE">
|
||||
</CopyFile>
|
||||
<RemoveFile Id="RemoveConfig" Directory="INSTALLDIR" Name="dnscrypt-proxy.toml" On="uninstall" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id="Complete" Level="1">
|
||||
<ComponentRef Id="ApplicationFiles" />
|
||||
<ComponentRef Id="ConfigInstall" />
|
||||
</Feature>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
|
@ -15,17 +16,13 @@ type CaptivePortalEntryIPs []net.IP
|
|||
type CaptivePortalMap map[string]CaptivePortalEntryIPs
|
||||
|
||||
type CaptivePortalHandler struct {
|
||||
wg sync.WaitGroup
|
||||
cancelChannel chan struct{}
|
||||
countChannel chan struct{}
|
||||
channelCount int
|
||||
}
|
||||
|
||||
func (captivePortalHandler *CaptivePortalHandler) Stop() {
|
||||
close(captivePortalHandler.cancelChannel)
|
||||
for len(captivePortalHandler.countChannel) < captivePortalHandler.channelCount {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
close(captivePortalHandler.countChannel)
|
||||
captivePortalHandler.wg.Wait()
|
||||
}
|
||||
|
||||
func (ipsMap *CaptivePortalMap) GetEntry(msg *dns.Msg) (*dns.Question, *CaptivePortalEntryIPs) {
|
||||
|
@ -120,24 +117,29 @@ func handleColdStartClient(clientPc *net.UDPConn, cancelChannel chan struct{}, i
|
|||
}
|
||||
|
||||
func addColdStartListener(
|
||||
proxy *Proxy,
|
||||
ipsMap *CaptivePortalMap,
|
||||
listenAddrStr string,
|
||||
captivePortalHandler *CaptivePortalHandler,
|
||||
) error {
|
||||
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
|
||||
network := "udp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
network = "udp4"
|
||||
}
|
||||
listenUDPAddr, err := net.ResolveUDPAddr(network, listenAddrStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientPc, err := net.ListenUDP("udp", listenUDPAddr)
|
||||
clientPc, err := net.ListenUDP(network, listenUDPAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
captivePortalHandler.wg.Add(1)
|
||||
go func() {
|
||||
for !handleColdStartClient(clientPc, captivePortalHandler.cancelChannel, ipsMap) {
|
||||
}
|
||||
clientPc.Close()
|
||||
captivePortalHandler.countChannel <- struct{}{}
|
||||
captivePortalHandler.wg.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
@ -185,14 +187,17 @@ func ColdStart(proxy *Proxy) (*CaptivePortalHandler, error) {
|
|||
listenAddrStrs := proxy.listenAddresses
|
||||
captivePortalHandler := CaptivePortalHandler{
|
||||
cancelChannel: make(chan struct{}),
|
||||
countChannel: make(chan struct{}, len(listenAddrStrs)),
|
||||
channelCount: 0,
|
||||
}
|
||||
ok := false
|
||||
for _, listenAddrStr := range listenAddrStrs {
|
||||
if err := addColdStartListener(proxy, &ipsMap, listenAddrStr, &captivePortalHandler); err == nil {
|
||||
captivePortalHandler.channelCount++
|
||||
err = addColdStartListener(&ipsMap, listenAddrStr, &captivePortalHandler)
|
||||
if err == nil {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
err = nil
|
||||
}
|
||||
proxy.captivePortalMap = &ipsMap
|
||||
return &captivePortalHandler, nil
|
||||
return &captivePortalHandler, err
|
||||
}
|
||||
|
|
|
@ -6,9 +6,12 @@ import (
|
|||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
)
|
||||
|
||||
type CryptoConstruction uint16
|
||||
|
@ -162,3 +165,33 @@ func ReadTextFile(filename string) (string, error) {
|
|||
bin = bytes.TrimPrefix(bin, []byte{0xef, 0xbb, 0xbf})
|
||||
return string(bin), nil
|
||||
}
|
||||
|
||||
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
|
||||
|
||||
func maybeWritableByOtherUsers(p string) (bool, string, error) {
|
||||
p = path.Clean(p)
|
||||
for p != "/" && p != "." {
|
||||
st, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return false, p, err
|
||||
}
|
||||
mode := st.Mode()
|
||||
if mode.Perm()&2 != 0 && !(st.IsDir() && mode&os.ModeSticky == os.ModeSticky) {
|
||||
return true, p, nil
|
||||
}
|
||||
p = path.Dir(p)
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
func WarnIfMaybeWritableByOtherUsers(p string) {
|
||||
if ok, px, err := maybeWritableByOtherUsers(p); ok {
|
||||
if px == p {
|
||||
dlog.Criticalf("[%s] is writable by other system users - If this is not intentional, it is recommended to fix the access permissions", p)
|
||||
} else {
|
||||
dlog.Warnf("[%s] can be modified by other system users because [%s] is writable by other users - If this is not intentional, it is recommended to fix the access permissions", p, px)
|
||||
}
|
||||
} else if err != nil {
|
||||
dlog.Warnf("Error while checking if [%s] is accessible: [%s] : [%s]", p, px, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ type Config struct {
|
|||
Timeout int `toml:"timeout"`
|
||||
KeepAlive int `toml:"keepalive"`
|
||||
Proxy string `toml:"proxy"`
|
||||
CertRefreshConcurrency int `toml:"cert_refresh_concurrency"`
|
||||
CertRefreshDelay int `toml:"cert_refresh_delay"`
|
||||
CertIgnoreTimestamp bool `toml:"cert_ignore_timestamp"`
|
||||
EphemeralKeys bool `toml:"dnscrypt_ephemeral_keys"`
|
||||
|
@ -116,6 +117,7 @@ func newConfig() Config {
|
|||
LocalDoH: LocalDoHConfig{Path: "/dns-query"},
|
||||
Timeout: 5000,
|
||||
KeepAlive: 5,
|
||||
CertRefreshConcurrency: 10,
|
||||
CertRefreshDelay: 240,
|
||||
HTTP3: false,
|
||||
CertIgnoreTimestamp: false,
|
||||
|
@ -259,7 +261,7 @@ type ServerSummary struct {
|
|||
IPv6 bool `json:"ipv6"`
|
||||
Addrs []string `json:"addrs,omitempty"`
|
||||
Ports []int `json:"ports"`
|
||||
DNSSEC bool `json:"dnssec"`
|
||||
DNSSEC *bool `json:"dnssec,omitempty"`
|
||||
NoLog bool `json:"nolog"`
|
||||
NoFilter bool `json:"nofilter"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
@ -290,6 +292,7 @@ type ConfigFlags struct {
|
|||
Resolve *string
|
||||
List *bool
|
||||
ListAll *bool
|
||||
IncludeRelays *bool
|
||||
JSONOutput *bool
|
||||
Check *bool
|
||||
ConfigFile *string
|
||||
|
@ -323,6 +326,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
*flags.ConfigFile,
|
||||
)
|
||||
}
|
||||
WarnIfMaybeWritableByOtherUsers(foundConfigFile)
|
||||
config := newConfig()
|
||||
md, err := toml.DecodeFile(foundConfigFile, &config)
|
||||
if err != nil {
|
||||
|
@ -436,6 +440,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
if config.ForceTCP {
|
||||
proxy.mainProto = "tcp"
|
||||
}
|
||||
proxy.certRefreshConcurrency = Max(1, config.CertRefreshConcurrency)
|
||||
proxy.certRefreshDelay = time.Duration(Max(60, config.CertRefreshDelay)) * time.Minute
|
||||
proxy.certRefreshDelayAfterFailure = time.Duration(10 * time.Second)
|
||||
proxy.certIgnoreTimestamp = config.CertIgnoreTimestamp
|
||||
|
@ -742,7 +747,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
}
|
||||
}
|
||||
if *flags.List || *flags.ListAll {
|
||||
if err := config.printRegisteredServers(proxy, *flags.JSONOutput); err != nil {
|
||||
if err := config.printRegisteredServers(proxy, *flags.JSONOutput, *flags.IncludeRelays); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
|
@ -778,8 +783,47 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool) error {
|
||||
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool, includeRelays bool) error {
|
||||
var summary []ServerSummary
|
||||
if includeRelays {
|
||||
for _, registeredRelay := range proxy.registeredRelays {
|
||||
addrStr, port := registeredRelay.stamp.ServerAddrStr, stamps.DefaultPort
|
||||
var hostAddr string
|
||||
hostAddr, port = ExtractHostAndPort(addrStr, port)
|
||||
addrs := make([]string, 0)
|
||||
if (registeredRelay.stamp.Proto == stamps.StampProtoTypeDoH || registeredRelay.stamp.Proto == stamps.StampProtoTypeODoHTarget) &&
|
||||
len(registeredRelay.stamp.ProviderName) > 0 {
|
||||
providerName := registeredRelay.stamp.ProviderName
|
||||
var host string
|
||||
host, port = ExtractHostAndPort(providerName, port)
|
||||
addrs = append(addrs, host)
|
||||
}
|
||||
if len(addrStr) > 0 {
|
||||
addrs = append(addrs, hostAddr)
|
||||
}
|
||||
nolog := true
|
||||
nofilter := true
|
||||
if registeredRelay.stamp.Proto == stamps.StampProtoTypeODoHRelay {
|
||||
nolog = registeredRelay.stamp.Props&stamps.ServerInformalPropertyNoLog != 0
|
||||
}
|
||||
serverSummary := ServerSummary{
|
||||
Name: registeredRelay.name,
|
||||
Proto: registeredRelay.stamp.Proto.String(),
|
||||
IPv6: strings.HasPrefix(addrStr, "["),
|
||||
Ports: []int{port},
|
||||
Addrs: addrs,
|
||||
NoLog: nolog,
|
||||
NoFilter: nofilter,
|
||||
Description: registeredRelay.description,
|
||||
Stamp: registeredRelay.stamp.String(),
|
||||
}
|
||||
if jsonOutput {
|
||||
summary = append(summary, serverSummary)
|
||||
} else {
|
||||
fmt.Println(serverSummary.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, registeredServer := range proxy.registeredServers {
|
||||
addrStr, port := registeredServer.stamp.ServerAddrStr, stamps.DefaultPort
|
||||
var hostAddr string
|
||||
|
@ -795,13 +839,14 @@ func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool) erro
|
|||
if len(addrStr) > 0 {
|
||||
addrs = append(addrs, hostAddr)
|
||||
}
|
||||
dnssec := registeredServer.stamp.Props&stamps.ServerInformalPropertyDNSSEC != 0
|
||||
serverSummary := ServerSummary{
|
||||
Name: registeredServer.name,
|
||||
Proto: registeredServer.stamp.Proto.String(),
|
||||
IPv6: strings.HasPrefix(addrStr, "["),
|
||||
Ports: []int{port},
|
||||
Addrs: addrs,
|
||||
DNSSEC: registeredServer.stamp.Props&stamps.ServerInformalPropertyDNSSEC != 0,
|
||||
DNSSEC: &dnssec,
|
||||
NoLog: registeredServer.stamp.Props&stamps.ServerInformalPropertyNoLog != 0,
|
||||
NoFilter: registeredServer.stamp.Props&stamps.ServerInformalPropertyNoFilter != 0,
|
||||
Description: registeredServer.description,
|
||||
|
|
|
@ -183,7 +183,7 @@ func FetchCurrentDNSCryptCert(
|
|||
certCountStr = " - additional certificate"
|
||||
}
|
||||
if certInfo.CryptoConstruction == UndefinedConstruction {
|
||||
return certInfo, 0, fragmentsBlocked, errors.New("No useable certificate found")
|
||||
return certInfo, 0, fragmentsBlocked, errors.New("No usable certificate found")
|
||||
}
|
||||
return certInfo, int(rtt.Nanoseconds() / 1000000), fragmentsBlocked, nil
|
||||
}
|
||||
|
|
|
@ -274,8 +274,6 @@ func removeEDNS0Options(msg *dns.Msg) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
|
||||
|
||||
func dddToByte(s []byte) byte {
|
||||
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
|
||||
}
|
||||
|
|
|
@ -183,6 +183,12 @@ keepalive = 30
|
|||
# use_syslog = true
|
||||
|
||||
|
||||
## The maximum concurrency to reload certificates from the resolvers.
|
||||
## Default is 10.
|
||||
|
||||
# cert_refresh_concurrency = 10
|
||||
|
||||
|
||||
## Delay, in minutes, after which certificates are reloaded
|
||||
|
||||
cert_refresh_delay = 240
|
||||
|
@ -751,6 +757,14 @@ format = 'tsv'
|
|||
# cache_file = 'parental-control.md'
|
||||
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
|
||||
|
||||
### dnscry.pt servers - See https://www.dnscry.pt
|
||||
|
||||
# [sources.dnscry-pt-resolvers]
|
||||
# urls = ["https://www.dnscry.pt/resolvers.md"]
|
||||
# minisign_key = "RWQM31Nwkqh01x88SvrBL8djp1NH56Rb4mKLHz16K7qsXgEomnDv6ziQ"
|
||||
# cache_file = "dnscry.pt-resolvers.md"
|
||||
# refresh_delay = 72
|
||||
# prefix = "dnscry.pt-"
|
||||
|
||||
|
||||
#########################################
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
## Forward queries for example.com and *.example.com to 9.9.9.9 and 8.8.8.8
|
||||
# example.com 9.9.9.9,8.8.8.8
|
||||
|
||||
## Forward queries to a resolver using IPv6
|
||||
# ipv6.example.com [2001:DB8::42]:53
|
||||
|
||||
## Forward queries for .onion names to a local Tor client
|
||||
## Tor must be configured with the following in the torrc file:
|
||||
## DNSPort 9053
|
||||
|
|
|
@ -51,6 +51,7 @@ func main() {
|
|||
flags.Resolve = flag.String("resolve", "", "resolve a DNS name (string can be <name> or <name>,<resolver address>)")
|
||||
flags.List = flag.Bool("list", false, "print the list of available resolvers for the enabled filters")
|
||||
flags.ListAll = flag.Bool("list-all", false, "print the complete list of available resolvers, ignoring filters")
|
||||
flags.IncludeRelays = flag.Bool("include-relays", false, "include the list of available relays in the output of -list and -list-all")
|
||||
flags.JSONOutput = flag.Bool("json", false, "output list as JSON")
|
||||
flags.Check = flag.Bool("check", false, "check the configuration file and exit")
|
||||
flags.ConfigFile = flag.String("config", DefaultConfigFileName, "Path to the configuration file")
|
||||
|
@ -65,6 +66,10 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
if fullexecpath, err := os.Executable(); err == nil {
|
||||
WarnIfMaybeWritableByOtherUsers(fullexecpath)
|
||||
}
|
||||
|
||||
app := &App{
|
||||
flags: &flags,
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ var undelegatedSet = []string{
|
|||
"envoy",
|
||||
"example",
|
||||
"f.f.ip6.arpa",
|
||||
"fritz.box",
|
||||
"grp",
|
||||
"gw==",
|
||||
"home",
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/miekg/dns"
|
||||
sieve "github.com/opencoff/go-sieve"
|
||||
)
|
||||
|
||||
const StaleResponseTTL = 30 * time.Second
|
||||
|
@ -19,7 +19,7 @@ type CachedResponse struct {
|
|||
|
||||
type CachedResponses struct {
|
||||
sync.RWMutex
|
||||
cache *lru.ARCCache
|
||||
cache *sieve.Sieve[[32]byte, CachedResponse]
|
||||
}
|
||||
|
||||
var cachedResponses CachedResponses
|
||||
|
@ -75,12 +75,11 @@ func (plugin *PluginCache) Eval(pluginsState *PluginsState, msg *dns.Msg) error
|
|||
cachedResponses.RUnlock()
|
||||
return nil
|
||||
}
|
||||
cachedAny, ok := cachedResponses.cache.Get(cacheKey)
|
||||
cached, ok := cachedResponses.cache.Get(cacheKey)
|
||||
if !ok {
|
||||
cachedResponses.RUnlock()
|
||||
return nil
|
||||
}
|
||||
cached := cachedAny.(CachedResponse)
|
||||
expiration := cached.expiration
|
||||
synth := cached.msg.Copy()
|
||||
cachedResponses.RUnlock()
|
||||
|
@ -151,8 +150,8 @@ func (plugin *PluginCacheResponse) Eval(pluginsState *PluginsState, msg *dns.Msg
|
|||
cachedResponses.Lock()
|
||||
if cachedResponses.cache == nil {
|
||||
var err error
|
||||
cachedResponses.cache, err = lru.NewARC(pluginsState.cacheSize)
|
||||
if err != nil {
|
||||
cachedResponses.cache = sieve.New[[32]byte, CachedResponse](pluginsState.cacheSize)
|
||||
if cachedResponses.cache == nil {
|
||||
cachedResponses.Unlock()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func (plugin *PluginDNS64) Init(proxy *Proxy) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dlog.Infof("Registered DNS64 prefix [%s]", pref.String())
|
||||
dlog.Noticef("Registered DNS64 prefix [%s]", pref.String())
|
||||
plugin.pref64 = append(plugin.pref64, pref)
|
||||
}
|
||||
} else if len(proxy.dns64Resolvers) != 0 {
|
||||
|
@ -57,7 +57,10 @@ func (plugin *PluginDNS64) Init(proxy *Proxy) error {
|
|||
if err := plugin.refreshPref64(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
dlog.Notice("DNS64 map enabled")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -105,7 +108,7 @@ func (plugin *PluginDNS64) Eval(pluginsState *PluginsState, msg *dns.Msg) error
|
|||
return err
|
||||
}
|
||||
|
||||
if err != nil || resp.Rcode != dns.RcodeSuccess {
|
||||
if resp.Rcode != dns.RcodeSuccess {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -152,11 +155,10 @@ func (plugin *PluginDNS64) Eval(pluginsState *PluginsState, msg *dns.Msg) error
|
|||
}
|
||||
}
|
||||
|
||||
synth := EmptyResponseFromMessage(msg)
|
||||
synth.Answer = append(synth.Answer, synth64...)
|
||||
msg.Answer = synth64
|
||||
msg.AuthenticatedData = false
|
||||
msg.SetEdns0(uint16(MaxDNSUDPSafePacketSize), false)
|
||||
|
||||
pluginsState.synthResponse = synth
|
||||
pluginsState.action = PluginsActionSynth
|
||||
pluginsState.returnCode = PluginsReturnCodeCloak
|
||||
|
||||
return nil
|
||||
|
|
|
@ -49,9 +49,16 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
|
|||
var servers []string
|
||||
for _, server := range strings.Split(serversStr, ",") {
|
||||
server = strings.TrimSpace(server)
|
||||
if net.ParseIP(server) != nil {
|
||||
server = fmt.Sprintf("%s:%d", server, 53)
|
||||
server = strings.TrimPrefix(server, "[")
|
||||
server = strings.TrimSuffix(server, "]")
|
||||
if ip := net.ParseIP(server); ip != nil {
|
||||
if ip.To4() != nil {
|
||||
server = fmt.Sprintf("%s:%d", server, 53)
|
||||
} else {
|
||||
server = fmt.Sprintf("[%s]:%d", server, 53)
|
||||
}
|
||||
}
|
||||
dlog.Infof("Forwarding [%s] to %s", domain, server)
|
||||
servers = append(servers, server)
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
|
@ -82,8 +89,9 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
|
|||
if candidateLen > qNameLen {
|
||||
continue
|
||||
}
|
||||
if qName[qNameLen-candidateLen:] == candidate.domain &&
|
||||
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.')) {
|
||||
if (qName[qNameLen-candidateLen:] == candidate.domain &&
|
||||
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.'))) ||
|
||||
(candidate.domain == ".") {
|
||||
servers = candidate.servers
|
||||
break
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ type Proxy struct {
|
|||
certRefreshDelayAfterFailure time.Duration
|
||||
timeout time.Duration
|
||||
certRefreshDelay time.Duration
|
||||
certRefreshConcurrency int
|
||||
cacheSize int
|
||||
logMaxBackups int
|
||||
logMaxAge int
|
||||
|
@ -117,11 +118,18 @@ func (proxy *Proxy) registerLocalDoHListener(listener *net.TCPListener) {
|
|||
}
|
||||
|
||||
func (proxy *Proxy) addDNSListener(listenAddrStr string) {
|
||||
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
|
||||
udp := "udp"
|
||||
tcp := "tcp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
udp = "udp4"
|
||||
tcp = "tcp4"
|
||||
}
|
||||
listenUDPAddr, err := net.ResolveUDPAddr(udp, listenAddrStr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr)
|
||||
listenTCPAddr, err := net.ResolveTCPAddr(tcp, listenAddrStr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
|
@ -140,11 +148,11 @@ func (proxy *Proxy) addDNSListener(listenAddrStr string) {
|
|||
// if 'userName' is set and we are the parent process
|
||||
if !proxy.child {
|
||||
// parent
|
||||
listenerUDP, err := net.ListenUDP("udp", listenUDPAddr)
|
||||
listenerUDP, err := net.ListenUDP(udp, listenUDPAddr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr)
|
||||
listenerTCP, err := net.ListenTCP(tcp, listenTCPAddr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
|
@ -185,7 +193,12 @@ func (proxy *Proxy) addDNSListener(listenAddrStr string) {
|
|||
}
|
||||
|
||||
func (proxy *Proxy) addLocalDoHListener(listenAddrStr string) {
|
||||
listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr)
|
||||
network := "tcp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
listenTCPAddr, err := net.ResolveTCPAddr(network, listenAddrStr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
|
@ -201,7 +214,7 @@ func (proxy *Proxy) addLocalDoHListener(listenAddrStr string) {
|
|||
// if 'userName' is set and we are the parent process
|
||||
if !proxy.child {
|
||||
// parent
|
||||
listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr)
|
||||
listenerTCP, err := net.ListenTCP(network, listenTCPAddr)
|
||||
if err != nil {
|
||||
dlog.Fatal(err)
|
||||
}
|
||||
|
@ -441,7 +454,13 @@ func (proxy *Proxy) udpListenerFromAddr(listenAddr *net.UDPAddr) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientPc, err := listenConfig.ListenPacket(context.Background(), "udp", listenAddr.String())
|
||||
listenAddrStr := listenAddr.String()
|
||||
network := "udp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
network = "udp4"
|
||||
}
|
||||
clientPc, err := listenConfig.ListenPacket(context.Background(), network, listenAddrStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -455,7 +474,13 @@ func (proxy *Proxy) tcpListenerFromAddr(listenAddr *net.TCPAddr) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acceptPc, err := listenConfig.Listen(context.Background(), "tcp", listenAddr.String())
|
||||
listenAddrStr := listenAddr.String()
|
||||
network := "tcp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
acceptPc, err := listenConfig.Listen(context.Background(), network, listenAddrStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -469,7 +494,13 @@ func (proxy *Proxy) localDoHListenerFromAddr(listenAddr *net.TCPAddr) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acceptPc, err := listenConfig.Listen(context.Background(), "tcp", listenAddr.String())
|
||||
listenAddrStr := listenAddr.String()
|
||||
network := "tcp"
|
||||
isIPv4 := isDigit(listenAddrStr[0])
|
||||
if isIPv4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
acceptPc, err := listenConfig.Listen(context.Background(), network, listenAddrStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -517,7 +548,7 @@ func (proxy *Proxy) exchangeWithUDPServer(
|
|||
var pc net.Conn
|
||||
proxyDialer := proxy.xTransport.proxyDialer
|
||||
if proxyDialer == nil {
|
||||
pc, err = net.DialUDP("udp", nil, upstreamAddr)
|
||||
pc, err = net.DialTimeout("udp", upstreamAddr.String(), serverInfo.Timeout)
|
||||
} else {
|
||||
pc, err = (*proxyDialer).Dial("udp", upstreamAddr.String())
|
||||
}
|
||||
|
@ -560,7 +591,7 @@ func (proxy *Proxy) exchangeWithTCPServer(
|
|||
var pc net.Conn
|
||||
proxyDialer := proxy.xTransport.proxyDialer
|
||||
if proxyDialer == nil {
|
||||
pc, err = net.DialTCP("tcp", nil, upstreamAddr)
|
||||
pc, err = net.DialTimeout("tcp", upstreamAddr.String(), serverInfo.Timeout)
|
||||
} else {
|
||||
pc, err = (*proxyDialer).Dial("tcp", upstreamAddr.String())
|
||||
}
|
||||
|
|
|
@ -224,17 +224,34 @@ func (serversInfo *ServersInfo) refresh(proxy *Proxy) (int, error) {
|
|||
dlog.Debug("Refreshing certificates")
|
||||
serversInfo.RLock()
|
||||
// Appending registeredServers slice from sources may allocate new memory.
|
||||
registeredServers := make([]RegisteredServer, len(serversInfo.registeredServers))
|
||||
serversCount := len(serversInfo.registeredServers)
|
||||
registeredServers := make([]RegisteredServer, serversCount)
|
||||
copy(registeredServers, serversInfo.registeredServers)
|
||||
serversInfo.RUnlock()
|
||||
countChannel := make(chan struct{}, proxy.certRefreshConcurrency)
|
||||
errorChannel := make(chan error, serversCount)
|
||||
for i := range registeredServers {
|
||||
countChannel <- struct{}{}
|
||||
go func(registeredServer *RegisteredServer) {
|
||||
err := serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp)
|
||||
if err == nil {
|
||||
proxy.xTransport.internalResolverReady = true
|
||||
}
|
||||
errorChannel <- err
|
||||
<-countChannel
|
||||
}(®isteredServers[i])
|
||||
}
|
||||
liveServers := 0
|
||||
var err error
|
||||
for _, registeredServer := range registeredServers {
|
||||
if err = serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp); err == nil {
|
||||
for i := 0; i < serversCount; i++ {
|
||||
err = <-errorChannel
|
||||
if err == nil {
|
||||
liveServers++
|
||||
proxy.xTransport.internalResolverReady = true
|
||||
}
|
||||
}
|
||||
if liveServers > 0 {
|
||||
err = nil
|
||||
}
|
||||
serversInfo.Lock()
|
||||
sort.SliceStable(serversInfo.inner, func(i, j int) bool {
|
||||
return serversInfo.inner[i].initialRtt < serversInfo.inner[j].initialRtt
|
||||
|
@ -345,7 +362,7 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta
|
|||
server := proxy.serversInfo.registeredServers[serverIdx]
|
||||
proxy.serversInfo.RUnlock()
|
||||
|
||||
// Fall back to random relays until the logic is implementeed for non-DNSCrypt relays
|
||||
// Fall back to random relays until the logic is implemented for non-DNSCrypt relays
|
||||
if server.stamp.Proto == stamps.StampProtoTypeODoHTarget {
|
||||
candidates := make([]int, 0)
|
||||
for relayIdx, relayStamp := range relayStamps {
|
||||
|
@ -854,10 +871,17 @@ func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, i
|
|||
if msg.Rcode != dns.RcodeNameError {
|
||||
dlog.Criticalf("[%s] may be a lying resolver", name)
|
||||
}
|
||||
|
||||
protocol := tls.NegotiatedProtocol
|
||||
if len(protocol) == 0 {
|
||||
protocol = "http/1.x"
|
||||
protocol := "http"
|
||||
tlsVersion := uint16(0)
|
||||
tlsCipherSuite := uint16(0)
|
||||
if tls != nil {
|
||||
protocol = tls.NegotiatedProtocol
|
||||
if len(protocol) == 0 {
|
||||
protocol = "http/1.x"
|
||||
} else {
|
||||
tlsVersion = tls.Version
|
||||
tlsCipherSuite = tls.CipherSuite
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(protocol, "http/1.") {
|
||||
dlog.Warnf("[%s] does not support HTTP/2", name)
|
||||
|
@ -865,37 +889,39 @@ func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, i
|
|||
dlog.Infof(
|
||||
"[%s] TLS version: %x - Protocol: %v - Cipher suite: %v",
|
||||
name,
|
||||
tls.Version,
|
||||
tlsVersion,
|
||||
protocol,
|
||||
tls.CipherSuite,
|
||||
tlsCipherSuite,
|
||||
)
|
||||
showCerts := proxy.showCerts
|
||||
found := false
|
||||
var wantedHash [32]byte
|
||||
for _, cert := range tls.PeerCertificates {
|
||||
h := sha256.Sum256(cert.RawTBSCertificate)
|
||||
if showCerts {
|
||||
dlog.Noticef("Advertised relay cert: [%s] [%x]", cert.Subject, h)
|
||||
} else {
|
||||
dlog.Debugf("Advertised relay cert: [%s] [%x]", cert.Subject, h)
|
||||
}
|
||||
for _, hash := range stamp.Hashes {
|
||||
if len(hash) == len(wantedHash) {
|
||||
copy(wantedHash[:], hash)
|
||||
if h == wantedHash {
|
||||
found = true
|
||||
break
|
||||
if tls != nil {
|
||||
for _, cert := range tls.PeerCertificates {
|
||||
h := sha256.Sum256(cert.RawTBSCertificate)
|
||||
if showCerts {
|
||||
dlog.Noticef("Advertised relay cert: [%s] [%x]", cert.Subject, h)
|
||||
} else {
|
||||
dlog.Debugf("Advertised relay cert: [%s] [%x]", cert.Subject, h)
|
||||
}
|
||||
for _, hash := range stamp.Hashes {
|
||||
if len(hash) == len(wantedHash) {
|
||||
copy(wantedHash[:], hash)
|
||||
if h == wantedHash {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break
|
||||
if !found && len(stamp.Hashes) > 0 {
|
||||
dlog.Criticalf("[%s] Certificate hash [%x] not found", name, wantedHash)
|
||||
return ServerInfo{}, fmt.Errorf("Certificate hash not found")
|
||||
}
|
||||
}
|
||||
if !found && len(stamp.Hashes) > 0 {
|
||||
dlog.Criticalf("[%s] Certificate hash [%x] not found", name, wantedHash)
|
||||
return ServerInfo{}, fmt.Errorf("Certificate hash not found")
|
||||
}
|
||||
if len(serverResponse) < MinDNSPacketSize || len(serverResponse) > MaxDNSPacketSize ||
|
||||
serverResponse[0] != 0xca || serverResponse[1] != 0xfe || serverResponse[4] != 0x00 || serverResponse[5] != 0x01 {
|
||||
dlog.Info("Webserver returned an unexpected response")
|
||||
|
|
|
@ -131,7 +131,7 @@ func (source *Source) parseURLs(urls []string) {
|
|||
}
|
||||
|
||||
func fetchFromURL(xTransport *XTransport, u *url.URL) ([]byte, error) {
|
||||
bin, _, _, _, err := xTransport.Get(u, "", DefaultTimeout)
|
||||
bin, _, _, _, err := xTransport.GetWithCompression(u, "", DefaultTimeout)
|
||||
return bin, err
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ func loadTestSourceNames(t *testing.T, d *SourceTestData) {
|
|||
}
|
||||
}
|
||||
|
||||
func generateFixtureState(t *testing.T, d *SourceTestData, suffix, file string, state SourceTestState) {
|
||||
func generateFixtureState(_ *testing.T, d *SourceTestData, suffix, file string, state SourceTestState) {
|
||||
if _, ok := d.fixtures[state]; !ok {
|
||||
d.fixtures[state] = map[string]SourceFixture{}
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ func prepSourceTestCache(t *testing.T, d *SourceTestData, e *SourceTestExpect, s
|
|||
}
|
||||
|
||||
func prepSourceTestDownload(
|
||||
t *testing.T,
|
||||
_ *testing.T,
|
||||
d *SourceTestData,
|
||||
e *SourceTestExpect,
|
||||
source string,
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"crypto/tls"
|
||||
|
@ -482,6 +483,7 @@ func (xTransport *XTransport) Fetch(
|
|||
contentType string,
|
||||
body *[]byte,
|
||||
timeout time.Duration,
|
||||
compress bool,
|
||||
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
|
||||
if timeout <= 0 {
|
||||
timeout = xTransport.timeout
|
||||
|
@ -530,6 +532,9 @@ func (xTransport *XTransport) Fetch(
|
|||
)
|
||||
return nil, 0, nil, 0, err
|
||||
}
|
||||
if compress && body == nil {
|
||||
header["Accept-Encoding"] = []string{"gzip"}
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: method,
|
||||
URL: url,
|
||||
|
@ -596,7 +601,17 @@ func (xTransport *XTransport) Fetch(
|
|||
}
|
||||
}
|
||||
tls := resp.TLS
|
||||
bin, err := io.ReadAll(io.LimitReader(resp.Body, MaxHTTPBodyLength))
|
||||
|
||||
var bodyReader io.ReadCloser = resp.Body
|
||||
if compress && resp.Header.Get("Content-Encoding") == "gzip" {
|
||||
bodyReader, err = gzip.NewReader(io.LimitReader(resp.Body, MaxHTTPBodyLength))
|
||||
if err != nil {
|
||||
return nil, statusCode, tls, rtt, err
|
||||
}
|
||||
defer bodyReader.Close()
|
||||
}
|
||||
|
||||
bin, err := io.ReadAll(io.LimitReader(bodyReader, MaxHTTPBodyLength))
|
||||
if err != nil {
|
||||
return nil, statusCode, tls, rtt, err
|
||||
}
|
||||
|
@ -604,12 +619,20 @@ func (xTransport *XTransport) Fetch(
|
|||
return bin, statusCode, tls, rtt, err
|
||||
}
|
||||
|
||||
func (xTransport *XTransport) GetWithCompression(
|
||||
url *url.URL,
|
||||
accept string,
|
||||
timeout time.Duration,
|
||||
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
|
||||
return xTransport.Fetch("GET", url, accept, "", nil, timeout, true)
|
||||
}
|
||||
|
||||
func (xTransport *XTransport) Get(
|
||||
url *url.URL,
|
||||
accept string,
|
||||
timeout time.Duration,
|
||||
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
|
||||
return xTransport.Fetch("GET", url, accept, "", nil, timeout)
|
||||
return xTransport.Fetch("GET", url, accept, "", nil, timeout, false)
|
||||
}
|
||||
|
||||
func (xTransport *XTransport) Post(
|
||||
|
@ -619,7 +642,7 @@ func (xTransport *XTransport) Post(
|
|||
body *[]byte,
|
||||
timeout time.Duration,
|
||||
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
|
||||
return xTransport.Fetch("POST", url, accept, contentType, body, timeout)
|
||||
return xTransport.Fetch("POST", url, accept, contentType, body, timeout, false)
|
||||
}
|
||||
|
||||
func (xTransport *XTransport) dohLikeQuery(
|
||||
|
|
26
go.mod
26
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/dnscrypt/dnscrypt-proxy
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
|
@ -8,43 +8,43 @@ require (
|
|||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1
|
||||
github.com/hashicorp/golang-lru v1.0.2
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb
|
||||
github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3
|
||||
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20230211133001-124a632de565
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80
|
||||
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
|
||||
github.com/jedisct1/xsecretbox v0.0.0-20230811132812-b950633f9f1f
|
||||
github.com/k-sone/critbitgo v1.4.0
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/opencoff/go-sieve v0.2.1
|
||||
github.com/powerman/check v1.7.0
|
||||
github.com/quic-go/quic-go v0.37.4
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/sys v0.11.0
|
||||
github.com/quic-go/quic-go v0.43.1
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/go-syslog v1.0.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/powerman/deepequal v0.1.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
|
|
76
go.sum
76
go.sum
|
@ -13,15 +13,15 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA=
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
|
@ -32,9 +32,8 @@ github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwM
|
|||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM=
|
||||
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
|
@ -42,8 +41,8 @@ github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3 h1:3wOfILZqpvhb7bNBG
|
|||
github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3/go.mod h1:m25Jh2eJ0FXLWjMXFZItQ24W0HAIjosAe4Z0a+SZitU=
|
||||
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e h1:tzG4EjKgHIqKVkLIAC4pXTIapuM2BR05uXokEEysAXA=
|
||||
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e/go.mod h1:SAINchklztk2jcLWJ4bpNF4KnwDUSUTX+cJbspWC2Rw=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20230211133001-124a632de565 h1:BPBMaUCgtmiHvqgugbSuegXjADJfERsPbmRqgdq8Pjo=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20230211133001-124a632de565/go.mod h1:mEGEFZsGe4sG5Mb3Xi89pmsy+TZ0946ArbYMGKAM5uA=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774 h1:DobL5d8UxrYzlD0PbU/EVBAGHuDiFyH46gr6povMw50=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774/go.mod h1:mEGEFZsGe4sG5Mb3Xi89pmsy+TZ0946ArbYMGKAM5uA=
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80 h1:o6d/E+fxKQrSEOyR7H+Lb3iFMW3daWdq1nsTnQPWaF0=
|
||||
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80/go.mod h1:dwjlOg9tYzZlxyi9HAGJYKUlVe3RZIfXvcni4VUfqRA=
|
||||
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY=
|
||||
|
@ -56,11 +55,14 @@ github.com/k-sone/critbitgo v1.4.0 h1:l71cTyBGeh6X5ATh6Fibgw3+rtNT80BA0uNNWgkPrb
|
|||
github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s=
|
||||
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
|
||||
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/opencoff/go-sieve v0.2.1 h1:5Pv6rd3zRquNmXcYHFndjVoolTgcv0ua2XTdMQ+gw0M=
|
||||
github.com/opencoff/go-sieve v0.2.1/go.mod h1:CndxLpW4R8fDq04XfBSCOZ+qWwDCcxjfUJbr0GPqWHY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -71,10 +73,8 @@ github.com/powerman/deepequal v0.1.0 h1:sVwtyTsBuYIvdbLR1O2wzRY63YgPqdGZmk/o80l+
|
|||
github.com/powerman/deepequal v0.1.0/go.mod h1:3k7aG/slufBhUANdN67o/UPg8i5YaiJ6FmibWX0cn04=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
|
||||
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
||||
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
|
||||
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
|
@ -82,50 +82,35 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
|
@ -139,3 +124,4 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# This is the official list of people who can contribute (and typically
|
||||
# have contributed) code to the gomock repository.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# The submission process automatically checks to make sure
|
||||
# that people submitting code are listed in this file (by email address).
|
||||
#
|
||||
# Names should be added to this file only after verifying that
|
||||
# the individual or the individual's organization has agreed to
|
||||
# the appropriate Contributor License Agreement, found here:
|
||||
#
|
||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
#
|
||||
# The agreement for individuals can be filled out on the web.
|
||||
#
|
||||
# When adding J Random Contributor's name to this file,
|
||||
# either J's name or J's organization's name should be
|
||||
# added to the AUTHORS file, depending on whether the
|
||||
# individual or corporate CLA was used.
|
||||
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
#
|
||||
# An entry with two email addresses specifies that the
|
||||
# first address should be used in the submit logs and
|
||||
# that the second address should be recognized as the
|
||||
# same person when interacting with Rietveld.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Aaron Jacobs <jacobsa@google.com> <aaronjjacobs@gmail.com>
|
||||
Alex Reece <awreece@gmail.com>
|
||||
David Symonds <dsymonds@golang.org>
|
||||
Ryan Barrett <ryanb@google.com>
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.12
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func printModuleVersion() {
|
||||
log.Printf("No version information is available for Mockgen compiled with " +
|
||||
"version 1.11")
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
|
@ -1,30 +0,0 @@
|
|||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- revive
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- stylecheck
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gosimple
|
||||
- prealloc
|
||||
fast: false
|
||||
disable-all: true
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
exclude-use-default: false
|
|
@ -1,222 +0,0 @@
|
|||
package lru
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default2QRecentRatio is the ratio of the 2Q cache dedicated
|
||||
// to recently added entries that have only been accessed once.
|
||||
Default2QRecentRatio = 0.25
|
||||
|
||||
// Default2QGhostEntries is the default ratio of ghost
|
||||
// entries kept to track entries recently evicted
|
||||
Default2QGhostEntries = 0.50
|
||||
)
|
||||
|
||||
// TwoQueueCache is a thread-safe fixed size 2Q cache.
|
||||
// 2Q is an enhancement over the standard LRU cache
|
||||
// in that it tracks both frequently and recently used
|
||||
// entries separately. This avoids a burst in access to new
|
||||
// entries from evicting frequently used entries. It adds some
|
||||
// additional tracking overhead to the standard LRU cache, and is
|
||||
// computationally about 2x the cost, and adds some metadata over
|
||||
// head. The ARCCache is similar, but does not require setting any
|
||||
// parameters.
|
||||
type TwoQueueCache struct {
|
||||
size int
|
||||
recentSize int
|
||||
|
||||
recent simplelru.LRUCache
|
||||
frequent simplelru.LRUCache
|
||||
recentEvict simplelru.LRUCache
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New2Q creates a new TwoQueueCache using the default
|
||||
// values for the parameters.
|
||||
func New2Q(size int) (*TwoQueueCache, error) {
|
||||
return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
|
||||
}
|
||||
|
||||
// New2QParams creates a new TwoQueueCache using the provided
|
||||
// parameter values.
|
||||
func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, error) {
|
||||
if size <= 0 {
|
||||
return nil, fmt.Errorf("invalid size")
|
||||
}
|
||||
if recentRatio < 0.0 || recentRatio > 1.0 {
|
||||
return nil, fmt.Errorf("invalid recent ratio")
|
||||
}
|
||||
if ghostRatio < 0.0 || ghostRatio > 1.0 {
|
||||
return nil, fmt.Errorf("invalid ghost ratio")
|
||||
}
|
||||
|
||||
// Determine the sub-sizes
|
||||
recentSize := int(float64(size) * recentRatio)
|
||||
evictSize := int(float64(size) * ghostRatio)
|
||||
|
||||
// Allocate the LRUs
|
||||
recent, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frequent, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recentEvict, err := simplelru.NewLRU(evictSize, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the cache
|
||||
c := &TwoQueueCache{
|
||||
size: size,
|
||||
recentSize: recentSize,
|
||||
recent: recent,
|
||||
frequent: frequent,
|
||||
recentEvict: recentEvict,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check if this is a frequent value
|
||||
if val, ok := c.frequent.Get(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// If the value is contained in recent, then we
|
||||
// promote it to frequent
|
||||
if val, ok := c.recent.Peek(key); ok {
|
||||
c.recent.Remove(key)
|
||||
c.frequent.Add(key, val)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// No hit
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Add adds a value to the cache.
|
||||
func (c *TwoQueueCache) Add(key, value interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check if the value is frequently used already,
|
||||
// and just update the value
|
||||
if c.frequent.Contains(key) {
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the value is recently used, and promote
|
||||
// the value into the frequent list
|
||||
if c.recent.Contains(key) {
|
||||
c.recent.Remove(key)
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// If the value was recently evicted, add it to the
|
||||
// frequently used list
|
||||
if c.recentEvict.Contains(key) {
|
||||
c.ensureSpace(true)
|
||||
c.recentEvict.Remove(key)
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Add to the recently seen list
|
||||
c.ensureSpace(false)
|
||||
c.recent.Add(key, value)
|
||||
}
|
||||
|
||||
// ensureSpace is used to ensure we have space in the cache
|
||||
func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
|
||||
// If we have space, nothing to do
|
||||
recentLen := c.recent.Len()
|
||||
freqLen := c.frequent.Len()
|
||||
if recentLen+freqLen < c.size {
|
||||
return
|
||||
}
|
||||
|
||||
// If the recent buffer is larger than
|
||||
// the target, evict from there
|
||||
if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
|
||||
k, _, _ := c.recent.RemoveOldest()
|
||||
c.recentEvict.Add(k, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove from the frequent list otherwise
|
||||
c.frequent.RemoveOldest()
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *TwoQueueCache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.recent.Len() + c.frequent.Len()
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
// The frequently used keys are first in the returned slice.
|
||||
func (c *TwoQueueCache) Keys() []interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
k1 := c.frequent.Keys()
|
||||
k2 := c.recent.Keys()
|
||||
return append(k1, k2...)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *TwoQueueCache) Remove(key interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.frequent.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.recent.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.recentEvict.Remove(key) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *TwoQueueCache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.recent.Purge()
|
||||
c.frequent.Purge()
|
||||
c.recentEvict.Purge()
|
||||
}
|
||||
|
||||
// Contains is used to check if the cache contains a key
|
||||
// without updating recency or frequency.
|
||||
func (c *TwoQueueCache) Contains(key interface{}) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.frequent.Contains(key) || c.recent.Contains(key)
|
||||
}
|
||||
|
||||
// Peek is used to inspect the cache value of a key
|
||||
// without updating recency or frequency.
|
||||
func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if val, ok := c.frequent.Peek(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
return c.recent.Peek(key)
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
Copyright (c) 2014 HashiCorp, Inc.
|
||||
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
golang-lru
|
||||
==========
|
||||
|
||||
Please upgrade to github.com/hashicorp/golang-lru/v2 for all new code as v1 will
|
||||
not be updated anymore. The v2 version supports generics and is faster; old code
|
||||
can specify a specific tag, e.g. github.com/hashicorp/golang-lru/v1.0.2 for
|
||||
backwards compatibility.
|
|
@ -1,256 +0,0 @@
|
|||
package lru
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
)
|
||||
|
||||
// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
|
||||
// ARC is an enhancement over the standard LRU cache in that tracks both
|
||||
// frequency and recency of use. This avoids a burst in access to new
|
||||
// entries from evicting the frequently used older entries. It adds some
|
||||
// additional tracking overhead to a standard LRU cache, computationally
|
||||
// it is roughly 2x the cost, and the extra memory overhead is linear
|
||||
// with the size of the cache. ARC has been patented by IBM, but is
|
||||
// similar to the TwoQueueCache (2Q) which requires setting parameters.
|
||||
type ARCCache struct {
|
||||
size int // Size is the total capacity of the cache
|
||||
p int // P is the dynamic preference towards T1 or T2
|
||||
|
||||
t1 simplelru.LRUCache // T1 is the LRU for recently accessed items
|
||||
b1 simplelru.LRUCache // B1 is the LRU for evictions from t1
|
||||
|
||||
t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items
|
||||
b2 simplelru.LRUCache // B2 is the LRU for evictions from t2
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewARC creates an ARC of the given size
|
||||
func NewARC(size int) (*ARCCache, error) {
|
||||
// Create the sub LRUs
|
||||
b1, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t1, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t2, err := simplelru.NewLRU(size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the ARC
|
||||
c := &ARCCache{
|
||||
size: size,
|
||||
p: 0,
|
||||
t1: t1,
|
||||
b1: b1,
|
||||
t2: t2,
|
||||
b2: b2,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// If the value is contained in T1 (recent), then
|
||||
// promote it to T2 (frequent)
|
||||
if val, ok := c.t1.Peek(key); ok {
|
||||
c.t1.Remove(key)
|
||||
c.t2.Add(key, val)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Check if the value is contained in T2 (frequent)
|
||||
if val, ok := c.t2.Get(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// No hit
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Add adds a value to the cache.
|
||||
func (c *ARCCache) Add(key, value interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check if the value is contained in T1 (recent), and potentially
|
||||
// promote it to frequent T2
|
||||
if c.t1.Contains(key) {
|
||||
c.t1.Remove(key)
|
||||
c.t2.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the value is already in T2 (frequent) and update it
|
||||
if c.t2.Contains(key) {
|
||||
c.t2.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this value was recently evicted as part of the
|
||||
// recently used list
|
||||
if c.b1.Contains(key) {
|
||||
// T1 set is too small, increase P appropriately
|
||||
delta := 1
|
||||
b1Len := c.b1.Len()
|
||||
b2Len := c.b2.Len()
|
||||
if b2Len > b1Len {
|
||||
delta = b2Len / b1Len
|
||||
}
|
||||
if c.p+delta >= c.size {
|
||||
c.p = c.size
|
||||
} else {
|
||||
c.p += delta
|
||||
}
|
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size {
|
||||
c.replace(false)
|
||||
}
|
||||
|
||||
// Remove from B1
|
||||
c.b1.Remove(key)
|
||||
|
||||
// Add the key to the frequently used list
|
||||
c.t2.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this value was recently evicted as part of the
|
||||
// frequently used list
|
||||
if c.b2.Contains(key) {
|
||||
// T2 set is too small, decrease P appropriately
|
||||
delta := 1
|
||||
b1Len := c.b1.Len()
|
||||
b2Len := c.b2.Len()
|
||||
if b1Len > b2Len {
|
||||
delta = b1Len / b2Len
|
||||
}
|
||||
if delta >= c.p {
|
||||
c.p = 0
|
||||
} else {
|
||||
c.p -= delta
|
||||
}
|
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size {
|
||||
c.replace(true)
|
||||
}
|
||||
|
||||
// Remove from B2
|
||||
c.b2.Remove(key)
|
||||
|
||||
// Add the key to the frequently used list
|
||||
c.t2.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size {
|
||||
c.replace(false)
|
||||
}
|
||||
|
||||
// Keep the size of the ghost buffers trim
|
||||
if c.b1.Len() > c.size-c.p {
|
||||
c.b1.RemoveOldest()
|
||||
}
|
||||
if c.b2.Len() > c.p {
|
||||
c.b2.RemoveOldest()
|
||||
}
|
||||
|
||||
// Add to the recently seen list
|
||||
c.t1.Add(key, value)
|
||||
}
|
||||
|
||||
// replace is used to adaptively evict from either T1 or T2
|
||||
// based on the current learned value of P
|
||||
func (c *ARCCache) replace(b2ContainsKey bool) {
|
||||
t1Len := c.t1.Len()
|
||||
if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
|
||||
k, _, ok := c.t1.RemoveOldest()
|
||||
if ok {
|
||||
c.b1.Add(k, nil)
|
||||
}
|
||||
} else {
|
||||
k, _, ok := c.t2.RemoveOldest()
|
||||
if ok {
|
||||
c.b2.Add(k, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of cached entries
|
||||
func (c *ARCCache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.t1.Len() + c.t2.Len()
|
||||
}
|
||||
|
||||
// Keys returns all the cached keys
|
||||
func (c *ARCCache) Keys() []interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
k1 := c.t1.Keys()
|
||||
k2 := c.t2.Keys()
|
||||
return append(k1, k2...)
|
||||
}
|
||||
|
||||
// Remove is used to purge a key from the cache
|
||||
func (c *ARCCache) Remove(key interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.t1.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.t2.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.b1.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.b2.Remove(key) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Purge is used to clear the cache
|
||||
func (c *ARCCache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.t1.Purge()
|
||||
c.t2.Purge()
|
||||
c.b1.Purge()
|
||||
c.b2.Purge()
|
||||
}
|
||||
|
||||
// Contains is used to check if the cache contains a key
|
||||
// without updating recency or frequency.
|
||||
func (c *ARCCache) Contains(key interface{}) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.t1.Contains(key) || c.t2.Contains(key)
|
||||
}
|
||||
|
||||
// Peek is used to inspect the cache value of a key
|
||||
// without updating recency or frequency.
|
||||
func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if val, ok := c.t1.Peek(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
return c.t2.Peek(key)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Package lru provides three different LRU caches of varying sophistication.
|
||||
//
|
||||
// Cache is a simple LRU cache. It is based on the
|
||||
// LRU implementation in groupcache:
|
||||
// https://github.com/golang/groupcache/tree/master/lru
|
||||
//
|
||||
// TwoQueueCache tracks frequently used and recently used entries separately.
|
||||
// This avoids a burst of accesses from taking out frequently used entries,
|
||||
// at the cost of about 2x computational overhead and some extra bookkeeping.
|
||||
//
|
||||
// ARCCache is an adaptive replacement cache. It tracks recent evictions as
|
||||
// well as recent usage in both the frequent and recent caches. Its
|
||||
// computational overhead is comparable to TwoQueueCache, but the memory
|
||||
// overhead is linear with the size of the cache.
|
||||
//
|
||||
// ARC has been patented by IBM, so do not use it if that is problematic for
|
||||
// your program.
|
||||
//
|
||||
// All caches in this package take locks while operating, and are therefore
|
||||
// thread-safe for consumers.
|
||||
package lru
|
|
@ -1,231 +0,0 @@
|
|||
package lru
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
|
||||
DefaultEvictedBufferSize = 16
|
||||
)
|
||||
|
||||
// Cache is a thread-safe fixed size LRU cache.
|
||||
type Cache struct {
|
||||
lru *simplelru.LRU
|
||||
evictedKeys, evictedVals []interface{}
|
||||
onEvictedCB func(k, v interface{})
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates an LRU of the given size.
|
||||
func New(size int) (*Cache, error) {
|
||||
return NewWithEvict(size, nil)
|
||||
}
|
||||
|
||||
// NewWithEvict constructs a fixed size cache with the given eviction
|
||||
// callback.
|
||||
func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, err error) {
|
||||
// create a cache with default settings
|
||||
c = &Cache{
|
||||
onEvictedCB: onEvicted,
|
||||
}
|
||||
if onEvicted != nil {
|
||||
c.initEvictBuffers()
|
||||
onEvicted = c.onEvicted
|
||||
}
|
||||
c.lru, err = simplelru.NewLRU(size, onEvicted)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Cache) initEvictBuffers() {
|
||||
c.evictedKeys = make([]interface{}, 0, DefaultEvictedBufferSize)
|
||||
c.evictedVals = make([]interface{}, 0, DefaultEvictedBufferSize)
|
||||
}
|
||||
|
||||
// onEvicted save evicted key/val and sent in externally registered callback
|
||||
// outside of critical section
|
||||
func (c *Cache) onEvicted(k, v interface{}) {
|
||||
c.evictedKeys = append(c.evictedKeys, k)
|
||||
c.evictedVals = append(c.evictedVals, v)
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *Cache) Purge() {
|
||||
var ks, vs []interface{}
|
||||
c.lock.Lock()
|
||||
c.lru.Purge()
|
||||
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
|
||||
ks, vs = c.evictedKeys, c.evictedVals
|
||||
c.initEvictBuffers()
|
||||
}
|
||||
c.lock.Unlock()
|
||||
// invoke callback outside of critical section
|
||||
if c.onEvictedCB != nil {
|
||||
for i := 0; i < len(ks); i++ {
|
||||
c.onEvictedCB(ks[i], vs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||
func (c *Cache) Add(key, value interface{}) (evicted bool) {
|
||||
var k, v interface{}
|
||||
c.lock.Lock()
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.Lock()
|
||||
value, ok = c.lru.Get(key)
|
||||
c.lock.Unlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Contains checks if a key is in the cache, without updating the
|
||||
// recent-ness or deleting it for being stale.
|
||||
func (c *Cache) Contains(key interface{}) bool {
|
||||
c.lock.RLock()
|
||||
containKey := c.lru.Contains(key)
|
||||
c.lock.RUnlock()
|
||||
return containKey
|
||||
}
|
||||
|
||||
// Peek returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.RLock()
|
||||
value, ok = c.lru.Peek(key)
|
||||
c.lock.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// ContainsOrAdd checks if a key is in the cache without updating the
|
||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||
// Returns whether found and whether an eviction occurred.
|
||||
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
|
||||
var k, v interface{}
|
||||
c.lock.Lock()
|
||||
if c.lru.Contains(key) {
|
||||
c.lock.Unlock()
|
||||
return true, false
|
||||
}
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return false, evicted
|
||||
}
|
||||
|
||||
// PeekOrAdd checks if a key is in the cache without updating the
|
||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||
// Returns whether found and whether an eviction occurred.
|
||||
func (c *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) {
|
||||
var k, v interface{}
|
||||
c.lock.Lock()
|
||||
previous, ok = c.lru.Peek(key)
|
||||
if ok {
|
||||
c.lock.Unlock()
|
||||
return previous, true, false
|
||||
}
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return nil, false, evicted
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *Cache) Remove(key interface{}) (present bool) {
|
||||
var k, v interface{}
|
||||
c.lock.Lock()
|
||||
present = c.lru.Remove(key)
|
||||
if c.onEvictedCB != nil && present {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && present {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Resize changes the cache size.
|
||||
func (c *Cache) Resize(size int) (evicted int) {
|
||||
var ks, vs []interface{}
|
||||
c.lock.Lock()
|
||||
evicted = c.lru.Resize(size)
|
||||
if c.onEvictedCB != nil && evicted > 0 {
|
||||
ks, vs = c.evictedKeys, c.evictedVals
|
||||
c.initEvictBuffers()
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted > 0 {
|
||||
for i := 0; i < len(ks); i++ {
|
||||
c.onEvictedCB(ks[i], vs[i])
|
||||
}
|
||||
}
|
||||
return evicted
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) {
|
||||
var k, v interface{}
|
||||
c.lock.Lock()
|
||||
key, value, ok = c.lru.RemoveOldest()
|
||||
if c.onEvictedCB != nil && ok {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && ok {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *Cache) GetOldest() (key, value interface{}, ok bool) {
|
||||
c.lock.RLock()
|
||||
key, value, ok = c.lru.GetOldest()
|
||||
c.lock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *Cache) Keys() []interface{} {
|
||||
c.lock.RLock()
|
||||
keys := c.lru.Keys()
|
||||
c.lock.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
length := c.lru.Len()
|
||||
c.lock.RUnlock()
|
||||
return length
|
||||
}
|
|
@ -25,7 +25,7 @@ type entry struct {
|
|||
// NewLRU constructs an LRU of the given size
|
||||
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
|
||||
if size <= 0 {
|
||||
return nil, errors.New("must provide a positive size")
|
||||
return nil, errors.New("Must provide a positive size")
|
||||
}
|
||||
c := &LRU{
|
||||
size: size,
|
||||
|
@ -73,9 +73,6 @@ func (c *LRU) Add(key, value interface{}) (evicted bool) {
|
|||
func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(ent)
|
||||
if ent.Value.(*entry) == nil {
|
||||
return nil, false
|
||||
}
|
||||
return ent.Value.(*entry).value, true
|
||||
}
|
||||
return
|
||||
|
@ -109,7 +106,7 @@ func (c *LRU) Remove(key interface{}) (present bool) {
|
|||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) {
|
||||
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
|
||||
ent := c.evictList.Back()
|
||||
if ent != nil {
|
||||
c.removeElement(ent)
|
||||
|
@ -120,7 +117,7 @@ func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) {
|
|||
}
|
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *LRU) GetOldest() (key, value interface{}, ok bool) {
|
||||
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) {
|
||||
ent := c.evictList.Back()
|
||||
if ent != nil {
|
||||
kv := ent.Value.(*entry)
|
||||
|
@ -145,19 +142,6 @@ func (c *LRU) Len() int {
|
|||
return c.evictList.Len()
|
||||
}
|
||||
|
||||
// Resize changes the cache size.
|
||||
func (c *LRU) Resize(size int) (evicted int) {
|
||||
diff := c.Len() - size
|
||||
if diff < 0 {
|
||||
diff = 0
|
||||
}
|
||||
for i := 0; i < diff; i++ {
|
||||
c.removeOldest()
|
||||
}
|
||||
c.size = size
|
||||
return diff
|
||||
}
|
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (c *LRU) removeOldest() {
|
||||
ent := c.evictList.Back()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// Package simplelru provides simple LRU implementation based on build-in container/list.
|
||||
package simplelru
|
||||
|
||||
// LRUCache is the interface for simple LRU cache.
|
||||
|
@ -11,7 +10,7 @@ type LRUCache interface {
|
|||
// updates the "recently used"-ness of the key. #value, isFound
|
||||
Get(key interface{}) (value interface{}, ok bool)
|
||||
|
||||
// Checks if a key exists in cache without updating the recent-ness.
|
||||
// Check if a key exsists in cache without updating the recent-ness.
|
||||
Contains(key interface{}) (ok bool)
|
||||
|
||||
// Returns key's value without updating the "recently used"-ness of the key.
|
||||
|
@ -32,9 +31,6 @@ type LRUCache interface {
|
|||
// Returns the number of items in the cache.
|
||||
Len() int
|
||||
|
||||
// Clears all cache entries.
|
||||
// Clear all cache entries
|
||||
Purge()
|
||||
|
||||
// Resizes cache, returning number evicted
|
||||
Resize(int) int
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package lru
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getRand(tb testing.TB) int64 {
|
||||
out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
return out.Int64()
|
||||
}
|
|
@ -11,7 +11,11 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const DefaultPort = 443
|
||||
const (
|
||||
DefaultPort = 443
|
||||
DefaultDNSPort = 53
|
||||
StampScheme = "sdns://"
|
||||
)
|
||||
|
||||
type ServerInformalProperties uint64
|
||||
|
||||
|
@ -97,7 +101,10 @@ func NewServerStampFromString(stampStr string) (ServerStamp, error) {
|
|||
if len(bin) < 1 {
|
||||
return ServerStamp{}, errors.New("Stamp is too short")
|
||||
}
|
||||
if bin[0] == uint8(StampProtoTypeDNSCrypt) {
|
||||
|
||||
if bin[0] == uint8(StampProtoTypePlain) {
|
||||
return newPlainDNSServerStamp(bin)
|
||||
} else if bin[0] == uint8(StampProtoTypeDNSCrypt) {
|
||||
return newDNSCryptServerStamp(bin)
|
||||
} else if bin[0] == uint8(StampProtoTypeDoH) {
|
||||
return newDoHServerStamp(bin)
|
||||
|
@ -112,7 +119,7 @@ func NewServerStampFromString(stampStr string) (ServerStamp, error) {
|
|||
}
|
||||
|
||||
func NewRelayAndServerStampFromString(stampStr string) (ServerStamp, ServerStamp, error) {
|
||||
if !strings.HasPrefix(stampStr, "sdns://") {
|
||||
if !strings.HasPrefix(stampStr, StampScheme) {
|
||||
return ServerStamp{}, ServerStamp{}, errors.New("Stamps are expected to start with \"sdns://\"")
|
||||
}
|
||||
stampStr = stampStr[7:]
|
||||
|
@ -120,11 +127,11 @@ func NewRelayAndServerStampFromString(stampStr string) (ServerStamp, ServerStamp
|
|||
if len(parts) != 2 {
|
||||
return ServerStamp{}, ServerStamp{}, errors.New("This is not a relay+server stamp")
|
||||
}
|
||||
relayStamp, err := NewServerStampFromString("sdns://" + parts[0])
|
||||
relayStamp, err := NewServerStampFromString(StampScheme + parts[0])
|
||||
if err != nil {
|
||||
return ServerStamp{}, ServerStamp{}, err
|
||||
}
|
||||
serverStamp, err := NewServerStampFromString("sdns://" + parts[1])
|
||||
serverStamp, err := NewServerStampFromString(StampScheme + parts[1])
|
||||
if err != nil {
|
||||
return ServerStamp{}, ServerStamp{}, err
|
||||
}
|
||||
|
@ -137,6 +144,49 @@ func NewRelayAndServerStampFromString(stampStr string) (ServerStamp, ServerStamp
|
|||
return relayStamp, serverStamp, nil
|
||||
}
|
||||
|
||||
// id(u8)=0x00 props 0x00 addrLen(1) serverAddr
|
||||
func newPlainDNSServerStamp(bin []byte) (ServerStamp, error) {
|
||||
stamp := ServerStamp{Proto: StampProtoTypePlain}
|
||||
if len(bin) < 1+8+1+1 {
|
||||
return stamp, errors.New("Stamp is too short")
|
||||
}
|
||||
stamp.Props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9]))
|
||||
binLen := len(bin)
|
||||
pos := 9
|
||||
|
||||
length := int(bin[pos])
|
||||
if 1+length > binLen-pos {
|
||||
return stamp, errors.New("Invalid stamp")
|
||||
}
|
||||
pos++
|
||||
stamp.ServerAddrStr = string(bin[pos : pos+length])
|
||||
pos += length
|
||||
|
||||
colIndex := strings.LastIndex(stamp.ServerAddrStr, ":")
|
||||
bracketIndex := strings.LastIndex(stamp.ServerAddrStr, "]")
|
||||
if colIndex < bracketIndex {
|
||||
colIndex = -1
|
||||
}
|
||||
if colIndex < 0 {
|
||||
colIndex = len(stamp.ServerAddrStr) // DefaultDNSPort
|
||||
stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultDNSPort)
|
||||
}
|
||||
if colIndex >= len(stamp.ServerAddrStr)-1 {
|
||||
return stamp, errors.New("Invalid stamp (empty port)")
|
||||
}
|
||||
ipOnly := stamp.ServerAddrStr[:colIndex]
|
||||
if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil {
|
||||
return stamp, errors.New("Invalid stamp (port range)")
|
||||
}
|
||||
if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil {
|
||||
return stamp, errors.New("Invalid stamp (IP address)")
|
||||
}
|
||||
if pos != binLen {
|
||||
return stamp, errors.New("Invalid stamp (garbage after end)")
|
||||
}
|
||||
return stamp, nil
|
||||
}
|
||||
|
||||
// id(u8)=0x01 props addrLen(1) serverAddr pkStrlen(1) pkStr providerNameLen(1) providerName
|
||||
|
||||
func newDNSCryptServerStamp(bin []byte) (ServerStamp, error) {
|
||||
|
@ -169,8 +219,7 @@ func newDNSCryptServerStamp(bin []byte) (ServerStamp, error) {
|
|||
return stamp, errors.New("Invalid stamp (empty port)")
|
||||
}
|
||||
ipOnly := stamp.ServerAddrStr[:colIndex]
|
||||
portOnly := stamp.ServerAddrStr[colIndex+1:]
|
||||
if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil {
|
||||
if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil {
|
||||
return stamp, errors.New("Invalid stamp (port range)")
|
||||
}
|
||||
if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil {
|
||||
|
@ -268,8 +317,7 @@ func newDoHServerStamp(bin []byte) (ServerStamp, error) {
|
|||
return stamp, errors.New("Invalid stamp (empty port)")
|
||||
}
|
||||
ipOnly := stamp.ServerAddrStr[:colIndex]
|
||||
portOnly := stamp.ServerAddrStr[colIndex+1:]
|
||||
if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil {
|
||||
if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil {
|
||||
return stamp, errors.New("Invalid stamp (port range)")
|
||||
}
|
||||
if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil {
|
||||
|
@ -344,8 +392,7 @@ func newDNSCryptRelayStamp(bin []byte) (ServerStamp, error) {
|
|||
return stamp, errors.New("Invalid stamp (empty port)")
|
||||
}
|
||||
ipOnly := stamp.ServerAddrStr[:colIndex]
|
||||
portOnly := stamp.ServerAddrStr[colIndex+1:]
|
||||
if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil {
|
||||
if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil {
|
||||
return stamp, errors.New("Invalid stamp (port range)")
|
||||
}
|
||||
if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil {
|
||||
|
@ -426,8 +473,7 @@ func newODoHRelayStamp(bin []byte) (ServerStamp, error) {
|
|||
return stamp, errors.New("Invalid stamp (empty port)")
|
||||
}
|
||||
ipOnly := stamp.ServerAddrStr[:colIndex]
|
||||
portOnly := stamp.ServerAddrStr[colIndex+1:]
|
||||
if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil {
|
||||
if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil {
|
||||
return stamp, errors.New("Invalid stamp (port range)")
|
||||
}
|
||||
if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil {
|
||||
|
@ -438,8 +484,17 @@ func newODoHRelayStamp(bin []byte) (ServerStamp, error) {
|
|||
return stamp, nil
|
||||
}
|
||||
|
||||
func validatePort(port string) error {
|
||||
if _, err := strconv.ParseUint(port, 10, 16); err != nil {
|
||||
return errors.New("Invalid port")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) String() string {
|
||||
if stamp.Proto == StampProtoTypeDNSCrypt {
|
||||
if stamp.Proto == StampProtoTypePlain {
|
||||
return stamp.plainStrng()
|
||||
} else if stamp.Proto == StampProtoTypeDNSCrypt {
|
||||
return stamp.dnsCryptString()
|
||||
} else if stamp.Proto == StampProtoTypeDoH {
|
||||
return stamp.dohString()
|
||||
|
@ -453,6 +508,22 @@ func (stamp *ServerStamp) String() string {
|
|||
panic("Unsupported protocol")
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) plainStrng() string {
|
||||
bin := make([]uint8, 9)
|
||||
bin[0] = uint8(StampProtoTypePlain)
|
||||
binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props))
|
||||
|
||||
serverAddrStr := stamp.ServerAddrStr
|
||||
if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultDNSPort)) {
|
||||
serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultDNSPort))]
|
||||
}
|
||||
bin = append(bin, uint8(len(serverAddrStr)))
|
||||
bin = append(bin, []uint8(serverAddrStr)...)
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return StampScheme + str
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) dnsCryptString() string {
|
||||
bin := make([]uint8, 9)
|
||||
bin[0] = uint8(StampProtoTypeDNSCrypt)
|
||||
|
@ -473,7 +544,7 @@ func (stamp *ServerStamp) dnsCryptString() string {
|
|||
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return "sdns://" + str
|
||||
return StampScheme + str
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) dohString() string {
|
||||
|
@ -510,7 +581,7 @@ func (stamp *ServerStamp) dohString() string {
|
|||
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return "sdns://" + str
|
||||
return StampScheme + str
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) oDohTargetString() string {
|
||||
|
@ -526,7 +597,7 @@ func (stamp *ServerStamp) oDohTargetString() string {
|
|||
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return "sdns://" + str
|
||||
return StampScheme + str
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) dnsCryptRelayString() string {
|
||||
|
@ -542,7 +613,7 @@ func (stamp *ServerStamp) dnsCryptRelayString() string {
|
|||
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return "sdns://" + str
|
||||
return StampScheme + str
|
||||
}
|
||||
|
||||
func (stamp *ServerStamp) oDohRelayString() string {
|
||||
|
@ -579,5 +650,5 @@ func (stamp *ServerStamp) oDohRelayString() string {
|
|||
|
||||
str := base64.RawURLEncoding.EncodeToString(bin)
|
||||
|
||||
return "sdns://" + str
|
||||
return StampScheme + str
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
|||
* https://addr.tools/
|
||||
* https://dnscheck.tools/
|
||||
* https://github.com/egbakou/domainverifier
|
||||
* https://github.com/semihalev/sdns
|
||||
* https://github.com/wintbiit/NineDNS
|
||||
* https://linuxcontainers.org/incus/
|
||||
* https://ifconfig.es
|
||||
|
||||
|
||||
Send pull request if you want to be listed here.
|
||||
|
@ -124,6 +128,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
|||
*all of them*
|
||||
|
||||
* 103{4,5} - DNS standard
|
||||
* 1183 - ISDN, X25 and other deprecated records
|
||||
* 1348 - NSAP record (removed the record)
|
||||
* 1982 - Serial Arithmetic
|
||||
* 1876 - LOC record
|
||||
|
|
|
@ -10,8 +10,6 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
|||
//
|
||||
// * opcode isn't OpcodeQuery or OpcodeNotify
|
||||
//
|
||||
// * Zero bit isn't zero
|
||||
//
|
||||
// * does not have exactly 1 question in the question section
|
||||
//
|
||||
// * has more than 1 RR in the Answer section
|
||||
|
|
|
@ -22,8 +22,7 @@ func (dns *Msg) SetReply(request *Msg) *Msg {
|
|||
}
|
||||
dns.Rcode = RcodeSuccess
|
||||
if len(request.Question) > 0 {
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = request.Question[0]
|
||||
dns.Question = []Question{request.Question[0]}
|
||||
}
|
||||
return dns
|
||||
}
|
||||
|
@ -199,10 +198,12 @@ func IsDomainName(s string) (labels int, ok bool) {
|
|||
off int
|
||||
begin int
|
||||
wasDot bool
|
||||
escape bool
|
||||
)
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\\':
|
||||
escape = !escape
|
||||
if off+1 > lenmsg {
|
||||
return labels, false
|
||||
}
|
||||
|
@ -218,6 +219,7 @@ func IsDomainName(s string) (labels int, ok bool) {
|
|||
|
||||
wasDot = false
|
||||
case '.':
|
||||
escape = false
|
||||
if i == 0 && len(s) > 1 {
|
||||
// leading dots are not legal except for the root zone
|
||||
return labels, false
|
||||
|
@ -244,10 +246,13 @@ func IsDomainName(s string) (labels int, ok bool) {
|
|||
labels++
|
||||
begin = i + 1
|
||||
default:
|
||||
escape = false
|
||||
wasDot = false
|
||||
}
|
||||
}
|
||||
|
||||
if escape {
|
||||
return labels, false
|
||||
}
|
||||
return labels, true
|
||||
}
|
||||
|
||||
|
@ -292,26 +297,19 @@ func IsFqdn(s string) bool {
|
|||
return (len(s)-i)%2 != 0
|
||||
}
|
||||
|
||||
// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
|
||||
// This means the RRs need to have the same type, name, and class. Returns true
|
||||
// if the RR set is valid, otherwise false.
|
||||
// IsRRset reports whether a set of RRs is a valid RRset as defined by RFC 2181.
|
||||
// This means the RRs need to have the same type, name, and class.
|
||||
func IsRRset(rrset []RR) bool {
|
||||
if len(rrset) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(rrset) == 1 {
|
||||
return true
|
||||
}
|
||||
rrHeader := rrset[0].Header()
|
||||
rrType := rrHeader.Rrtype
|
||||
rrClass := rrHeader.Class
|
||||
rrName := rrHeader.Name
|
||||
|
||||
baseH := rrset[0].Header()
|
||||
for _, rr := range rrset[1:] {
|
||||
curRRHeader := rr.Header()
|
||||
if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
|
||||
curH := rr.Header()
|
||||
if curH.Rrtype != baseH.Rrtype || curH.Class != baseH.Class || curH.Name != baseH.Name {
|
||||
// Mismatch between the records, so this is not a valid rrset for
|
||||
//signing/verifying
|
||||
// signing/verifying
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -329,9 +327,15 @@ func Fqdn(s string) string {
|
|||
}
|
||||
|
||||
// CanonicalName returns the domain name in canonical form. A name in canonical
|
||||
// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
|
||||
// form is lowercase and fully qualified. Only US-ASCII letters are affected. See
|
||||
// Section 6.2 in RFC 4034.
|
||||
func CanonicalName(s string) string {
|
||||
return strings.ToLower(Fqdn(s))
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
r += 'a' - 'A'
|
||||
}
|
||||
return r
|
||||
}, Fqdn(s))
|
||||
}
|
||||
|
||||
// Copied from the official Go code.
|
||||
|
|
|
@ -37,7 +37,8 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
|||
return nil, ErrPrivKey
|
||||
}
|
||||
// TODO(mg): check if the pubkey matches the private key
|
||||
algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8)
|
||||
algoStr, _, _ := strings.Cut(m["algorithm"], " ")
|
||||
algo, err := strconv.ParseUint(algoStr, 10, 8)
|
||||
if err != nil {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
|
@ -159,7 +160,7 @@ func parseKey(r io.Reader, file string) (map[string]string, error) {
|
|||
k = l.token
|
||||
case zValue:
|
||||
if k == "" {
|
||||
return nil, &ParseError{file, "no private key seen", l}
|
||||
return nil, &ParseError{file: file, err: "no private key seen", lex: l}
|
||||
}
|
||||
|
||||
m[strings.ToLower(k)] = l.token
|
||||
|
|
|
@ -185,7 +185,7 @@ func (rr *OPT) Do() bool {
|
|||
|
||||
// SetDo sets the DO (DNSSEC OK) bit.
|
||||
// If we pass an argument, set the DO bit to that value.
|
||||
// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored.
|
||||
// It is possible to pass 2 or more arguments, but they will be ignored.
|
||||
func (rr *OPT) SetDo(do ...bool) {
|
||||
if len(do) == 1 {
|
||||
if do[0] {
|
||||
|
@ -508,6 +508,7 @@ func (e *EDNS0_LLQ) String() string {
|
|||
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *EDNS0_LLQ) copy() EDNS0 {
|
||||
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
|
||||
}
|
||||
|
|
|
@ -35,17 +35,17 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
|||
token = token[:i]
|
||||
}
|
||||
|
||||
sx := strings.SplitN(token, "-", 2)
|
||||
if len(sx) != 2 {
|
||||
startStr, endStr, ok := strings.Cut(token, "-")
|
||||
if !ok {
|
||||
return zp.setParseError("bad start-stop in $GENERATE range", l)
|
||||
}
|
||||
|
||||
start, err := strconv.ParseInt(sx[0], 10, 64)
|
||||
start, err := strconv.ParseInt(startStr, 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad start in $GENERATE range", l)
|
||||
}
|
||||
|
||||
end, err := strconv.ParseInt(sx[1], 10, 64)
|
||||
end, err := strconv.ParseInt(endStr, 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad stop in $GENERATE range", l)
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
|||
}
|
||||
|
||||
// _BLANK
|
||||
l, ok := zp.c.Next()
|
||||
l, ok = zp.c.Next()
|
||||
if !ok || l.value != zBlank {
|
||||
return zp.setParseError("garbage after $GENERATE range", l)
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ func (r *generateReader) parseError(msg string, end int) *ParseError {
|
|||
l.token = r.s[r.si-1 : end]
|
||||
l.column += r.si // l.column starts one zBLANK before r.s
|
||||
|
||||
return &ParseError{r.file, msg, l}
|
||||
return &ParseError{file: r.file, err: msg, lex: l}
|
||||
}
|
||||
|
||||
func (r *generateReader) Read(p []byte) (int, error) {
|
||||
|
@ -211,15 +211,16 @@ func (r *generateReader) ReadByte() (byte, error) {
|
|||
func modToPrintf(s string) (string, int64, string) {
|
||||
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
||||
// values for optional width and type, if necessary.
|
||||
var offStr, widthStr, base string
|
||||
switch xs := strings.Split(s, ","); len(xs) {
|
||||
case 1:
|
||||
offStr, widthStr, base = xs[0], "0", "d"
|
||||
case 2:
|
||||
offStr, widthStr, base = xs[0], xs[1], "d"
|
||||
case 3:
|
||||
offStr, widthStr, base = xs[0], xs[1], xs[2]
|
||||
default:
|
||||
offStr, s, ok0 := strings.Cut(s, ",")
|
||||
widthStr, s, ok1 := strings.Cut(s, ",")
|
||||
base, _, ok2 := strings.Cut(s, ",")
|
||||
if !ok0 {
|
||||
widthStr = "0"
|
||||
}
|
||||
if !ok1 {
|
||||
base = "d"
|
||||
}
|
||||
if ok2 {
|
||||
return "", 0, "bad modifier in $GENERATE"
|
||||
}
|
||||
|
||||
|
@ -234,8 +235,8 @@ func modToPrintf(s string) (string, int64, string) {
|
|||
return "", 0, "bad offset in $GENERATE"
|
||||
}
|
||||
|
||||
width, err := strconv.ParseInt(widthStr, 10, 64)
|
||||
if err != nil || width < 0 || width > 255 {
|
||||
width, err := strconv.ParseUint(widthStr, 10, 8)
|
||||
if err != nil {
|
||||
return "", 0, "bad width in $GENERATE"
|
||||
}
|
||||
|
||||
|
|
|
@ -7,16 +7,18 @@ import "net"
|
|||
|
||||
const supportsReusePort = false
|
||||
|
||||
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||
if reuseport {
|
||||
func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
|
||||
if reuseport || reuseaddr {
|
||||
// TODO(tmthrgd): return an error?
|
||||
}
|
||||
|
||||
return net.Listen(network, addr)
|
||||
}
|
||||
|
||||
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||
if reuseport {
|
||||
const supportsReuseAddr = false
|
||||
|
||||
func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
|
||||
if reuseport || reuseaddr {
|
||||
// TODO(tmthrgd): return an error?
|
||||
}
|
||||
|
||||
|
|
|
@ -25,19 +25,41 @@ func reuseportControl(network, address string, c syscall.RawConn) error {
|
|||
return opErr
|
||||
}
|
||||
|
||||
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||
const supportsReuseAddr = true
|
||||
|
||||
func reuseaddrControl(network, address string, c syscall.RawConn) error {
|
||||
var opErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return opErr
|
||||
}
|
||||
|
||||
func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
|
||||
var lc net.ListenConfig
|
||||
if reuseport {
|
||||
switch {
|
||||
case reuseaddr && reuseport:
|
||||
case reuseport:
|
||||
lc.Control = reuseportControl
|
||||
case reuseaddr:
|
||||
lc.Control = reuseaddrControl
|
||||
}
|
||||
|
||||
return lc.Listen(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||
func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
|
||||
var lc net.ListenConfig
|
||||
if reuseport {
|
||||
switch {
|
||||
case reuseaddr && reuseport:
|
||||
case reuseport:
|
||||
lc.Control = reuseportControl
|
||||
case reuseaddr:
|
||||
lc.Control = reuseaddrControl
|
||||
}
|
||||
|
||||
return lc.ListenPacket(context.Background(), network, addr)
|
||||
|
|
|
@ -501,30 +501,28 @@ func packTxtString(s string, msg []byte, offset int) (int, error) {
|
|||
return offset, nil
|
||||
}
|
||||
|
||||
func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) {
|
||||
if offset >= len(msg) || len(s) > len(tmp) {
|
||||
func packOctetString(s string, msg []byte, offset int) (int, error) {
|
||||
if offset >= len(msg) || len(s) > 256*4+1 {
|
||||
return offset, ErrBuf
|
||||
}
|
||||
bs := tmp[:len(s)]
|
||||
copy(bs, s)
|
||||
for i := 0; i < len(bs); i++ {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if len(msg) <= offset {
|
||||
return offset, ErrBuf
|
||||
}
|
||||
if bs[i] == '\\' {
|
||||
if s[i] == '\\' {
|
||||
i++
|
||||
if i == len(bs) {
|
||||
if i == len(s) {
|
||||
break
|
||||
}
|
||||
// check for \DDD
|
||||
if isDDD(bs[i:]) {
|
||||
msg[offset] = dddToByte(bs[i:])
|
||||
if isDDD(s[i:]) {
|
||||
msg[offset] = dddToByte(s[i:])
|
||||
i += 2
|
||||
} else {
|
||||
msg[offset] = bs[i]
|
||||
msg[offset] = s[i]
|
||||
}
|
||||
} else {
|
||||
msg[offset] = bs[i]
|
||||
msg[offset] = s[i]
|
||||
}
|
||||
offset++
|
||||
}
|
||||
|
@ -716,7 +714,7 @@ func (h *MsgHdr) String() string {
|
|||
return s
|
||||
}
|
||||
|
||||
// Pack packs a Msg: it is converted to to wire format.
|
||||
// Pack packs a Msg: it is converted to wire format.
|
||||
// If the dns.Compress is true the message will be in compressed wire format.
|
||||
func (dns *Msg) Pack() (msg []byte, err error) {
|
||||
return dns.PackBuffer(nil)
|
||||
|
@ -896,23 +894,38 @@ func (dns *Msg) String() string {
|
|||
return "<nil> MsgHdr"
|
||||
}
|
||||
s := dns.MsgHdr.String() + " "
|
||||
s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
|
||||
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
||||
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
||||
if dns.MsgHdr.Opcode == OpcodeUpdate {
|
||||
s += "ZONE: " + strconv.Itoa(len(dns.Question)) + ", "
|
||||
s += "PREREQ: " + strconv.Itoa(len(dns.Answer)) + ", "
|
||||
s += "UPDATE: " + strconv.Itoa(len(dns.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
||||
} else {
|
||||
s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
|
||||
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
||||
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
||||
}
|
||||
opt := dns.IsEdns0()
|
||||
if opt != nil {
|
||||
// OPT PSEUDOSECTION
|
||||
s += opt.String() + "\n"
|
||||
}
|
||||
if len(dns.Question) > 0 {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
if dns.MsgHdr.Opcode == OpcodeUpdate {
|
||||
s += "\n;; ZONE SECTION:\n"
|
||||
} else {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
}
|
||||
for _, r := range dns.Question {
|
||||
s += r.String() + "\n"
|
||||
}
|
||||
}
|
||||
if len(dns.Answer) > 0 {
|
||||
s += "\n;; ANSWER SECTION:\n"
|
||||
if dns.MsgHdr.Opcode == OpcodeUpdate {
|
||||
s += "\n;; PREREQUISITE SECTION:\n"
|
||||
} else {
|
||||
s += "\n;; ANSWER SECTION:\n"
|
||||
}
|
||||
for _, r := range dns.Answer {
|
||||
if r != nil {
|
||||
s += r.String() + "\n"
|
||||
|
@ -920,7 +933,11 @@ func (dns *Msg) String() string {
|
|||
}
|
||||
}
|
||||
if len(dns.Ns) > 0 {
|
||||
s += "\n;; AUTHORITY SECTION:\n"
|
||||
if dns.MsgHdr.Opcode == OpcodeUpdate {
|
||||
s += "\n;; UPDATE SECTION:\n"
|
||||
} else {
|
||||
s += "\n;; AUTHORITY SECTION:\n"
|
||||
}
|
||||
for _, r := range dns.Ns {
|
||||
if r != nil {
|
||||
s += r.String() + "\n"
|
||||
|
|
|
@ -20,9 +20,7 @@ func unpackDataA(msg []byte, off int) (net.IP, int, error) {
|
|||
if off+net.IPv4len > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking a"}
|
||||
}
|
||||
a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...)
|
||||
off += net.IPv4len
|
||||
return a, off, nil
|
||||
return cloneSlice(msg[off : off+net.IPv4len]), off + net.IPv4len, nil
|
||||
}
|
||||
|
||||
func packDataA(a net.IP, msg []byte, off int) (int, error) {
|
||||
|
@ -47,9 +45,7 @@ func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
|
|||
if off+net.IPv6len > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking aaaa"}
|
||||
}
|
||||
aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...)
|
||||
off += net.IPv6len
|
||||
return aaaa, off, nil
|
||||
return cloneSlice(msg[off : off+net.IPv6len]), off + net.IPv6len, nil
|
||||
}
|
||||
|
||||
func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
|
||||
|
@ -410,29 +406,24 @@ func packStringTxt(s []string, msg []byte, off int) (int, error) {
|
|||
|
||||
func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
|
||||
var edns []EDNS0
|
||||
Option:
|
||||
var code uint16
|
||||
if off+4 > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
for off < len(msg) {
|
||||
if off+4 > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
code := binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
optlen := binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
if off+int(optlen) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
opt := makeDataOpt(code)
|
||||
if err := opt.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, opt)
|
||||
off += int(optlen)
|
||||
}
|
||||
code = binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
optlen := binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
if off+int(optlen) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
e := makeDataOpt(code)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
|
||||
if off < len(msg) {
|
||||
goto Option
|
||||
}
|
||||
|
||||
return edns, off, nil
|
||||
}
|
||||
|
||||
|
@ -461,8 +452,7 @@ func unpackStringOctet(msg []byte, off int) (string, int, error) {
|
|||
}
|
||||
|
||||
func packStringOctet(s string, msg []byte, off int) (int, error) {
|
||||
txtTmp := make([]byte, 256*4+1)
|
||||
off, err := packOctetString(s, msg, off, txtTmp)
|
||||
off, err := packOctetString(s, msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ Fetch:
|
|||
|
||||
err := r.Data.Parse(text)
|
||||
if err != nil {
|
||||
return &ParseError{"", err.Error(), l}
|
||||
return &ParseError{wrappedErr: err, lex: l}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -64,20 +66,26 @@ const (
|
|||
// ParseError is a parsing error. It contains the parse error and the location in the io.Reader
|
||||
// where the error occurred.
|
||||
type ParseError struct {
|
||||
file string
|
||||
err string
|
||||
lex lex
|
||||
file string
|
||||
err string
|
||||
wrappedErr error
|
||||
lex lex
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() (s string) {
|
||||
if e.file != "" {
|
||||
s = e.file + ": "
|
||||
}
|
||||
if e.err == "" && e.wrappedErr != nil {
|
||||
e.err = e.wrappedErr.Error()
|
||||
}
|
||||
s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
|
||||
strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
|
||||
return
|
||||
}
|
||||
|
||||
func (e *ParseError) Unwrap() error { return e.wrappedErr }
|
||||
|
||||
type lex struct {
|
||||
token string // text of the token
|
||||
err bool // when true, token text has lexer error
|
||||
|
@ -93,12 +101,13 @@ type ttlState struct {
|
|||
isByDirective bool // isByDirective indicates whether ttl was set by a $TTL directive
|
||||
}
|
||||
|
||||
// NewRR reads the RR contained in the string s. Only the first RR is returned.
|
||||
// NewRR reads a string s and returns the first RR.
|
||||
// If s contains no records, NewRR will return nil with no error.
|
||||
//
|
||||
// The class defaults to IN and TTL defaults to 3600. The full zone file syntax
|
||||
// like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are
|
||||
// set, except RR.Header().Rdlength which is set to 0.
|
||||
// The class defaults to IN, TTL defaults to 3600, and
|
||||
// origin for resolving relative domain names defaults to the DNS root (.).
|
||||
// Full zone file syntax is supported, including directives like $TTL and $ORIGIN.
|
||||
// All fields of the returned RR are set from the read data, except RR.Header().Rdlength which is set to 0.
|
||||
func NewRR(s string) (RR, error) {
|
||||
if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
|
||||
return ReadRR(strings.NewReader(s+"\n"), "")
|
||||
|
@ -168,8 +177,9 @@ type ZoneParser struct {
|
|||
// sub is used to parse $INCLUDE files and $GENERATE directives.
|
||||
// Next, by calling subNext, forwards the resulting RRs from this
|
||||
// sub parser to the calling code.
|
||||
sub *ZoneParser
|
||||
osFile *os.File
|
||||
sub *ZoneParser
|
||||
r io.Reader
|
||||
fsys fs.FS
|
||||
|
||||
includeDepth uint8
|
||||
|
||||
|
@ -188,7 +198,7 @@ func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
|
|||
if origin != "" {
|
||||
origin = Fqdn(origin)
|
||||
if _, ok := IsDomainName(origin); !ok {
|
||||
pe = &ParseError{file, "bad initial origin name", lex{}}
|
||||
pe = &ParseError{file: file, err: "bad initial origin name"}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,6 +230,24 @@ func (zp *ZoneParser) SetIncludeAllowed(v bool) {
|
|||
zp.includeAllowed = v
|
||||
}
|
||||
|
||||
// SetIncludeFS provides an [fs.FS] to use when looking for the target of
|
||||
// $INCLUDE directives. ($INCLUDE must still be enabled separately by calling
|
||||
// [ZoneParser.SetIncludeAllowed].) If fsys is nil, [os.Open] will be used.
|
||||
//
|
||||
// When fsys is an on-disk FS, the ability of $INCLUDE to reach files from
|
||||
// outside its root directory depends upon the FS implementation. For
|
||||
// instance, [os.DirFS] will refuse to open paths like "../../etc/passwd",
|
||||
// however it will still follow links which may point anywhere on the system.
|
||||
//
|
||||
// FS paths are slash-separated on all systems, even Windows. $INCLUDE paths
|
||||
// containing other characters such as backslash and colon may be accepted as
|
||||
// valid, but those characters will never be interpreted by an FS
|
||||
// implementation as path element separators. See [fs.ValidPath] for more
|
||||
// details.
|
||||
func (zp *ZoneParser) SetIncludeFS(fsys fs.FS) {
|
||||
zp.fsys = fsys
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF error that was encountered by the
|
||||
// ZoneParser.
|
||||
func (zp *ZoneParser) Err() error {
|
||||
|
@ -237,7 +265,7 @@ func (zp *ZoneParser) Err() error {
|
|||
}
|
||||
|
||||
func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
|
||||
zp.parseErr = &ParseError{zp.file, err, l}
|
||||
zp.parseErr = &ParseError{file: zp.file, err: err, lex: l}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
@ -260,9 +288,11 @@ func (zp *ZoneParser) subNext() (RR, bool) {
|
|||
return rr, true
|
||||
}
|
||||
|
||||
if zp.sub.osFile != nil {
|
||||
zp.sub.osFile.Close()
|
||||
zp.sub.osFile = nil
|
||||
if zp.sub.r != nil {
|
||||
if c, ok := zp.sub.r.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
zp.sub.r = nil
|
||||
}
|
||||
|
||||
if zp.sub.Err() != nil {
|
||||
|
@ -402,24 +432,44 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
|||
|
||||
// Start with the new file
|
||||
includePath := l.token
|
||||
if !filepath.IsAbs(includePath) {
|
||||
includePath = filepath.Join(filepath.Dir(zp.file), includePath)
|
||||
}
|
||||
|
||||
r1, e1 := os.Open(includePath)
|
||||
if e1 != nil {
|
||||
var as string
|
||||
if !filepath.IsAbs(l.token) {
|
||||
as = fmt.Sprintf(" as `%s'", includePath)
|
||||
var r1 io.Reader
|
||||
var e1 error
|
||||
if zp.fsys != nil {
|
||||
// fs.FS always uses / as separator, even on Windows, so use
|
||||
// path instead of filepath here:
|
||||
if !path.IsAbs(includePath) {
|
||||
includePath = path.Join(path.Dir(zp.file), includePath)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1)
|
||||
return zp.setParseError(msg, l)
|
||||
// os.DirFS, and probably others, expect all paths to be
|
||||
// relative, so clean the path and remove leading / if
|
||||
// present:
|
||||
includePath = strings.TrimLeft(path.Clean(includePath), "/")
|
||||
|
||||
r1, e1 = zp.fsys.Open(includePath)
|
||||
} else {
|
||||
if !filepath.IsAbs(includePath) {
|
||||
includePath = filepath.Join(filepath.Dir(zp.file), includePath)
|
||||
}
|
||||
r1, e1 = os.Open(includePath)
|
||||
}
|
||||
if e1 != nil {
|
||||
var as string
|
||||
if includePath != l.token {
|
||||
as = fmt.Sprintf(" as `%s'", includePath)
|
||||
}
|
||||
zp.parseErr = &ParseError{
|
||||
file: zp.file,
|
||||
wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", l.token, as, e1),
|
||||
lex: l,
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
zp.sub = NewZoneParser(r1, neworigin, includePath)
|
||||
zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1
|
||||
zp.sub.defttl, zp.sub.includeDepth, zp.sub.r = zp.defttl, zp.includeDepth+1, r1
|
||||
zp.sub.SetIncludeAllowed(true)
|
||||
zp.sub.SetIncludeFS(zp.fsys)
|
||||
return zp.subNext()
|
||||
case zExpectDirTTLBl:
|
||||
if l.value != zBlank {
|
||||
|
@ -605,8 +655,6 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
|||
if !isPrivate && zp.c.Peek().token == "" {
|
||||
// This is a dynamic update rr.
|
||||
|
||||
// TODO(tmthrgd): Previously slurpRemainder was only called
|
||||
// for certain RR types, which may have been important.
|
||||
if err := slurpRemainder(zp.c); err != nil {
|
||||
return zp.setParseError(err.err, err.lex)
|
||||
}
|
||||
|
@ -1216,42 +1264,34 @@ func stringToCm(token string) (e, m uint8, ok bool) {
|
|||
if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
|
||||
token = token[0 : len(token)-1]
|
||||
}
|
||||
s := strings.SplitN(token, ".", 2)
|
||||
var meters, cmeters, val int
|
||||
var err error
|
||||
switch len(s) {
|
||||
case 2:
|
||||
if cmeters, err = strconv.Atoi(s[1]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
meters, cmeters, val int
|
||||
err error
|
||||
)
|
||||
mStr, cmStr, hasCM := strings.Cut(token, ".")
|
||||
if hasCM {
|
||||
// There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12').
|
||||
// So we simply reject it.
|
||||
// We also make sure the first character is a digit to reject '+-' signs.
|
||||
if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' {
|
||||
cmeters, err = strconv.Atoi(cmStr)
|
||||
if err != nil || len(cmStr) > 2 || cmStr[0] < '0' || cmStr[0] > '9' {
|
||||
return
|
||||
}
|
||||
if len(s[1]) == 1 {
|
||||
if len(cmStr) == 1 {
|
||||
// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
|
||||
cmeters *= 10
|
||||
}
|
||||
if s[0] == "" {
|
||||
// This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
if meters, err = strconv.Atoi(s[0]); err != nil {
|
||||
return
|
||||
}
|
||||
// RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
|
||||
if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
|
||||
return
|
||||
}
|
||||
case 0:
|
||||
// huh?
|
||||
return 0, 0, false
|
||||
}
|
||||
ok = true
|
||||
// This slightly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
|
||||
if !hasCM || mStr != "" {
|
||||
meters, err = strconv.Atoi(mStr)
|
||||
// RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
|
||||
if err != nil || mStr[0] < '0' || mStr[0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if meters > 0 {
|
||||
e = 2
|
||||
val = meters
|
||||
|
@ -1263,8 +1303,7 @@ func stringToCm(token string) (e, m uint8, ok bool) {
|
|||
e++
|
||||
val /= 10
|
||||
}
|
||||
m = uint8(val)
|
||||
return
|
||||
return e, uint8(val), true
|
||||
}
|
||||
|
||||
func toAbsoluteName(name, origin string) (absolute string, ok bool) {
|
||||
|
@ -1337,12 +1376,12 @@ func slurpRemainder(c *zlexer) *ParseError {
|
|||
case zBlank:
|
||||
l, _ = c.Next()
|
||||
if l.value != zNewline && l.value != zEOF {
|
||||
return &ParseError{"", "garbage after rdata", l}
|
||||
return &ParseError{err: "garbage after rdata", lex: l}
|
||||
}
|
||||
case zNewline:
|
||||
case zEOF:
|
||||
default:
|
||||
return &ParseError{"", "garbage after rdata", l}
|
||||
return &ParseError{err: "garbage after rdata", lex: l}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1351,16 +1390,16 @@ func slurpRemainder(c *zlexer) *ParseError {
|
|||
// Used for NID and L64 record.
|
||||
func stringToNodeID(l lex) (uint64, *ParseError) {
|
||||
if len(l.token) < 19 {
|
||||
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
|
||||
}
|
||||
// There must be three colons at fixes positions, if not its a parse error
|
||||
if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
|
||||
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
|
||||
}
|
||||
s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
|
||||
u, err := strconv.ParseUint(s, 16, 64)
|
||||
if err != nil {
|
||||
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -226,6 +226,10 @@ type Server struct {
|
|||
// Whether to set the SO_REUSEPORT socket option, allowing multiple listeners to be bound to a single address.
|
||||
// It is only supported on certain GOOSes and when using ListenAndServe.
|
||||
ReusePort bool
|
||||
// Whether to set the SO_REUSEADDR socket option, allowing multiple listeners to be bound to a single address.
|
||||
// Crucially this allows binding when an existing server is listening on `0.0.0.0` or `::`.
|
||||
// It is only supported on certain GOOSes and when using ListenAndServe.
|
||||
ReuseAddr bool
|
||||
// AcceptMsgFunc will check the incoming message and will reject it early in the process.
|
||||
// By default DefaultMsgAcceptFunc will be used.
|
||||
MsgAcceptFunc MsgAcceptFunc
|
||||
|
@ -304,7 +308,7 @@ func (srv *Server) ListenAndServe() error {
|
|||
|
||||
switch srv.Net {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
l, err := listenTCP(srv.Net, addr, srv.ReusePort)
|
||||
l, err := listenTCP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -317,7 +321,7 @@ func (srv *Server) ListenAndServe() error {
|
|||
return errors.New("dns: neither Certificates nor GetCertificate set in Config")
|
||||
}
|
||||
network := strings.TrimSuffix(srv.Net, "-tls")
|
||||
l, err := listenTCP(network, addr, srv.ReusePort)
|
||||
l, err := listenTCP(network, addr, srv.ReusePort, srv.ReuseAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -327,7 +331,7 @@ func (srv *Server) ListenAndServe() error {
|
|||
unlock()
|
||||
return srv.serveTCP(l)
|
||||
case "udp", "udp4", "udp6":
|
||||
l, err := listenUDP(srv.Net, addr, srv.ReusePort)
|
||||
l, err := listenUDP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
l, _ := c.Next()
|
||||
i, e := strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
return &ParseError{l.token, "bad SVCB priority", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB priority", lex: l}
|
||||
}
|
||||
rr.Priority = uint16(i)
|
||||
|
||||
|
@ -95,7 +95,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
|
||||
name, nameOk := toAbsoluteName(l.token, o)
|
||||
if l.err || !nameOk {
|
||||
return &ParseError{l.token, "bad SVCB Target", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB Target", lex: l}
|
||||
}
|
||||
rr.Target = name
|
||||
|
||||
|
@ -111,7 +111,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
if !canHaveNextKey {
|
||||
// The key we can now read was probably meant to be
|
||||
// a part of the last value.
|
||||
return &ParseError{l.token, "bad SVCB value quotation", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB value quotation", lex: l}
|
||||
}
|
||||
|
||||
// In key=value pairs, value does not have to be quoted unless value
|
||||
|
@ -124,7 +124,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
// Key with no value and no equality sign
|
||||
key = l.token
|
||||
} else if idx == 0 {
|
||||
return &ParseError{l.token, "bad SVCB key", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
|
||||
} else {
|
||||
key, value = l.token[:idx], l.token[idx+1:]
|
||||
|
||||
|
@ -144,30 +144,30 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
value = l.token
|
||||
l, _ = c.Next()
|
||||
if l.value != zQuote {
|
||||
return &ParseError{l.token, "SVCB unterminated value", l}
|
||||
return &ParseError{file: l.token, err: "SVCB unterminated value", lex: l}
|
||||
}
|
||||
case zQuote:
|
||||
// There's nothing in double quotes.
|
||||
default:
|
||||
return &ParseError{l.token, "bad SVCB value", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB value", lex: l}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kv := makeSVCBKeyValue(svcbStringToKey(key))
|
||||
if kv == nil {
|
||||
return &ParseError{l.token, "bad SVCB key", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
|
||||
}
|
||||
if err := kv.parse(value); err != nil {
|
||||
return &ParseError{l.token, err.Error(), l}
|
||||
return &ParseError{file: l.token, wrappedErr: err, lex: l}
|
||||
}
|
||||
xs = append(xs, kv)
|
||||
case zQuote:
|
||||
return &ParseError{l.token, "SVCB key can't contain double quotes", l}
|
||||
return &ParseError{file: l.token, err: "SVCB key can't contain double quotes", lex: l}
|
||||
case zBlank:
|
||||
canHaveNextKey = true
|
||||
default:
|
||||
return &ParseError{l.token, "bad SVCB values", l}
|
||||
return &ParseError{file: l.token, err: "bad SVCB values", lex: l}
|
||||
}
|
||||
l, _ = c.Next()
|
||||
}
|
||||
|
@ -314,10 +314,11 @@ func (s *SVCBMandatory) unpack(b []byte) error {
|
|||
}
|
||||
|
||||
func (s *SVCBMandatory) parse(b string) error {
|
||||
str := strings.Split(b, ",")
|
||||
codes := make([]SVCBKey, 0, len(str))
|
||||
for _, e := range str {
|
||||
codes = append(codes, svcbStringToKey(e))
|
||||
codes := make([]SVCBKey, 0, strings.Count(b, ",")+1)
|
||||
for len(b) > 0 {
|
||||
var key string
|
||||
key, b, _ = strings.Cut(b, ",")
|
||||
codes = append(codes, svcbStringToKey(key))
|
||||
}
|
||||
s.Code = codes
|
||||
return nil
|
||||
|
@ -613,19 +614,24 @@ func (s *SVCBIPv4Hint) String() string {
|
|||
}
|
||||
|
||||
func (s *SVCBIPv4Hint) parse(b string) error {
|
||||
if b == "" {
|
||||
return errors.New("dns: svcbipv4hint: empty hint")
|
||||
}
|
||||
if strings.Contains(b, ":") {
|
||||
return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
|
||||
}
|
||||
str := strings.Split(b, ",")
|
||||
dst := make([]net.IP, len(str))
|
||||
for i, e := range str {
|
||||
|
||||
hint := make([]net.IP, 0, strings.Count(b, ",")+1)
|
||||
for len(b) > 0 {
|
||||
var e string
|
||||
e, b, _ = strings.Cut(b, ",")
|
||||
ip := net.ParseIP(e).To4()
|
||||
if ip == nil {
|
||||
return errors.New("dns: svcbipv4hint: bad ip")
|
||||
}
|
||||
dst[i] = ip
|
||||
hint = append(hint, ip)
|
||||
}
|
||||
s.Hint = dst
|
||||
s.Hint = hint
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -733,9 +739,14 @@ func (s *SVCBIPv6Hint) String() string {
|
|||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) parse(b string) error {
|
||||
str := strings.Split(b, ",")
|
||||
dst := make([]net.IP, len(str))
|
||||
for i, e := range str {
|
||||
if b == "" {
|
||||
return errors.New("dns: svcbipv6hint: empty hint")
|
||||
}
|
||||
|
||||
hint := make([]net.IP, 0, strings.Count(b, ",")+1)
|
||||
for len(b) > 0 {
|
||||
var e string
|
||||
e, b, _ = strings.Cut(b, ",")
|
||||
ip := net.ParseIP(e)
|
||||
if ip == nil {
|
||||
return errors.New("dns: svcbipv6hint: bad ip")
|
||||
|
@ -743,9 +754,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
|
|||
if ip.To4() != nil {
|
||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
|
||||
}
|
||||
dst[i] = ip
|
||||
hint = append(hint, ip)
|
||||
}
|
||||
s.Hint = dst
|
||||
s.Hint = hint
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -135,8 +135,8 @@ const (
|
|||
RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update]
|
||||
RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update]
|
||||
RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG]
|
||||
RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG]
|
||||
RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0]
|
||||
RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
|
||||
RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
|
||||
RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG]
|
||||
RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG]
|
||||
RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY]
|
||||
|
@ -236,6 +236,9 @@ var CertTypeToString = map[uint16]string{
|
|||
CertOID: "OID",
|
||||
}
|
||||
|
||||
// Prefix for IPv4 encoded as IPv6 address
|
||||
const ipv4InIPv6Prefix = "::ffff:"
|
||||
|
||||
//go:generate go run types_generate.go
|
||||
|
||||
// Question holds a DNS question. Usually there is just one. While the
|
||||
|
@ -399,6 +402,17 @@ func (rr *X25) String() string {
|
|||
return rr.Hdr.String() + rr.PSDNAddress
|
||||
}
|
||||
|
||||
// ISDN RR. See RFC 1183, Section 3.2.
|
||||
type ISDN struct {
|
||||
Hdr RR_Header
|
||||
Address string
|
||||
SubAddress string
|
||||
}
|
||||
|
||||
func (rr *ISDN) String() string {
|
||||
return rr.Hdr.String() + sprintTxt([]string{rr.Address, rr.SubAddress})
|
||||
}
|
||||
|
||||
// RT RR. See RFC 1183, Section 3.3.
|
||||
type RT struct {
|
||||
Hdr RR_Header
|
||||
|
@ -751,6 +765,11 @@ func (rr *AAAA) String() string {
|
|||
if rr.AAAA == nil {
|
||||
return rr.Hdr.String()
|
||||
}
|
||||
|
||||
if rr.AAAA.To4() != nil {
|
||||
return rr.Hdr.String() + ipv4InIPv6Prefix + rr.AAAA.String()
|
||||
}
|
||||
|
||||
return rr.Hdr.String() + rr.AAAA.String()
|
||||
}
|
||||
|
||||
|
@ -778,7 +797,7 @@ func (rr *GPOS) String() string {
|
|||
return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude
|
||||
}
|
||||
|
||||
// LOC RR. See RFC RFC 1876.
|
||||
// LOC RR. See RFC 1876.
|
||||
type LOC struct {
|
||||
Hdr RR_Header
|
||||
Version uint8
|
||||
|
@ -890,6 +909,11 @@ func (rr *RRSIG) String() string {
|
|||
return s
|
||||
}
|
||||
|
||||
// NXT RR. See RFC 2535.
|
||||
type NXT struct {
|
||||
NSEC
|
||||
}
|
||||
|
||||
// NSEC RR. See RFC 4034 and RFC 3755.
|
||||
type NSEC struct {
|
||||
Hdr RR_Header
|
||||
|
@ -974,7 +998,7 @@ func (rr *TALINK) String() string {
|
|||
sprintName(rr.PreviousName) + " " + sprintName(rr.NextName)
|
||||
}
|
||||
|
||||
// SSHFP RR. See RFC RFC 4255.
|
||||
// SSHFP RR. See RFC 4255.
|
||||
type SSHFP struct {
|
||||
Hdr RR_Header
|
||||
Algorithm uint8
|
||||
|
@ -988,7 +1012,7 @@ func (rr *SSHFP) String() string {
|
|||
" " + strings.ToUpper(rr.FingerPrint)
|
||||
}
|
||||
|
||||
// KEY RR. See RFC RFC 2535.
|
||||
// KEY RR. See RFC 2535.
|
||||
type KEY struct {
|
||||
DNSKEY
|
||||
}
|
||||
|
@ -1298,7 +1322,7 @@ type NINFO struct {
|
|||
|
||||
func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) }
|
||||
|
||||
// NID RR. See RFC RFC 6742.
|
||||
// NID RR. See RFC 6742.
|
||||
type NID struct {
|
||||
Hdr RR_Header
|
||||
Preference uint16
|
||||
|
@ -1517,7 +1541,7 @@ func (a *APLPrefix) str() string {
|
|||
case net.IPv6len:
|
||||
// add prefix for IPv4-mapped IPv6
|
||||
if v4 := a.Network.IP.To4(); v4 != nil {
|
||||
sb.WriteString("::ffff:")
|
||||
sb.WriteString(ipv4InIPv6Prefix)
|
||||
}
|
||||
sb.WriteString(a.Network.IP.String())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package dns
|
|||
import "fmt"
|
||||
|
||||
// Version is current version of this library.
|
||||
var Version = v{1, 1, 55}
|
||||
var Version = v{1, 1, 58}
|
||||
|
||||
// v holds the version of this library.
|
||||
type v struct {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
@ -20,6 +21,7 @@ type Transfer struct {
|
|||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
TsigSecret map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
tsigTimersOnly bool
|
||||
TLS *tls.Config // TLS config. If Xfr over TLS will be attempted
|
||||
}
|
||||
|
||||
func (t *Transfer) tsigProvider() TsigProvider {
|
||||
|
@ -57,7 +59,11 @@ func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
|||
}
|
||||
|
||||
if t.Conn == nil {
|
||||
t.Conn, err = DialTimeout("tcp", a, timeout)
|
||||
if t.TLS != nil {
|
||||
t.Conn, err = DialTimeoutWithTLS("tcp-tls", a, t.TLS, timeout)
|
||||
} else {
|
||||
t.Conn, err = DialTimeout("tcp", a, timeout)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,8 +86,13 @@ func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
|||
|
||||
func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) {
|
||||
first := true
|
||||
defer t.Close()
|
||||
defer close(c)
|
||||
defer func() {
|
||||
// First close the connection, then the channel. This allows functions blocked on
|
||||
// the channel to assume that the connection is closed and no further operations are
|
||||
// pending when they resume.
|
||||
t.Close()
|
||||
close(c)
|
||||
}()
|
||||
timeout := dnsTimeout
|
||||
if t.ReadTimeout != 0 {
|
||||
timeout = t.ReadTimeout
|
||||
|
@ -131,8 +142,13 @@ func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
|
|||
axfr := true
|
||||
n := 0
|
||||
qser := q.Ns[0].(*SOA).Serial
|
||||
defer t.Close()
|
||||
defer close(c)
|
||||
defer func() {
|
||||
// First close the connection, then the channel. This allows functions blocked on
|
||||
// the channel to assume that the connection is closed and no further operations are
|
||||
// pending when they resume.
|
||||
t.Close()
|
||||
close(c)
|
||||
}()
|
||||
timeout := dnsTimeout
|
||||
if t.ReadTimeout != 0 {
|
||||
timeout = t.ReadTimeout
|
||||
|
@ -172,7 +188,7 @@ func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
|
|||
if v, ok := rr.(*SOA); ok {
|
||||
if v.Serial == serial {
|
||||
n++
|
||||
// quit if it's a full axfr or the the servers' SOA is repeated the third time
|
||||
// quit if it's a full axfr or the servers' SOA is repeated the third time
|
||||
if axfr && n == 2 || n == 3 {
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
return
|
||||
|
|
|
@ -481,6 +481,21 @@ func (r1 *IPSECKEY) isDuplicate(_r2 RR) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (r1 *ISDN) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*ISDN)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.Address != r2.Address {
|
||||
return false
|
||||
}
|
||||
if r1.SubAddress != r2.SubAddress {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *KEY) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*KEY)
|
||||
if !ok {
|
||||
|
@ -871,6 +886,26 @@ func (r1 *NULL) isDuplicate(_r2 RR) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (r1 *NXT) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*NXT)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if !isDuplicateName(r1.NextDomain, r2.NextDomain) {
|
||||
return false
|
||||
}
|
||||
if len(r1.TypeBitMap) != len(r2.TypeBitMap) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(r1.TypeBitMap); i++ {
|
||||
if r1.TypeBitMap[i] != r2.TypeBitMap[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *OPENPGPKEY) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*OPENPGPKEY)
|
||||
if !ok {
|
||||
|
|
|
@ -372,6 +372,18 @@ func (rr *IPSECKEY) pack(msg []byte, off int, compression compressionMap, compre
|
|||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *ISDN) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||
off, err = packString(rr.Address, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packString(rr.SubAddress, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||
off, err = packUint16(rr.Flags, msg, off)
|
||||
if err != nil {
|
||||
|
@ -694,6 +706,18 @@ func (rr *NULL) pack(msg []byte, off int, compression compressionMap, compress b
|
|||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *NXT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||
off, err = packDomainName(rr.NextDomain, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packDataNsec(rr.TypeBitMap, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *OPENPGPKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||
off, err = packStringBase64(rr.PublicKey, msg, off)
|
||||
if err != nil {
|
||||
|
@ -1746,6 +1770,24 @@ func (rr *IPSECKEY) unpack(msg []byte, off int) (off1 int, err error) {
|
|||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *ISDN) unpack(msg []byte, off int) (off1 int, err error) {
|
||||
rdStart := off
|
||||
_ = rdStart
|
||||
|
||||
rr.Address, off, err = unpackString(msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
if off == len(msg) {
|
||||
return off, nil
|
||||
}
|
||||
rr.SubAddress, off, err = unpackString(msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) {
|
||||
rdStart := off
|
||||
_ = rdStart
|
||||
|
@ -2224,6 +2266,24 @@ func (rr *NULL) unpack(msg []byte, off int) (off1 int, err error) {
|
|||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *NXT) unpack(msg []byte, off int) (off1 int, err error) {
|
||||
rdStart := off
|
||||
_ = rdStart
|
||||
|
||||
rr.NextDomain, off, err = UnpackDomainName(msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
if off == len(msg) {
|
||||
return off, nil
|
||||
}
|
||||
rr.TypeBitMap, off, err = unpackDataNsec(msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (rr *OPENPGPKEY) unpack(msg []byte, off int) (off1 int, err error) {
|
||||
rdStart := off
|
||||
_ = rdStart
|
||||
|
|
|
@ -36,6 +36,7 @@ var TypeToRR = map[uint16]func() RR{
|
|||
TypeHIP: func() RR { return new(HIP) },
|
||||
TypeHTTPS: func() RR { return new(HTTPS) },
|
||||
TypeIPSECKEY: func() RR { return new(IPSECKEY) },
|
||||
TypeISDN: func() RR { return new(ISDN) },
|
||||
TypeKEY: func() RR { return new(KEY) },
|
||||
TypeKX: func() RR { return new(KX) },
|
||||
TypeL32: func() RR { return new(L32) },
|
||||
|
@ -59,6 +60,7 @@ var TypeToRR = map[uint16]func() RR{
|
|||
TypeNSEC3: func() RR { return new(NSEC3) },
|
||||
TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) },
|
||||
TypeNULL: func() RR { return new(NULL) },
|
||||
TypeNXT: func() RR { return new(NXT) },
|
||||
TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) },
|
||||
TypeOPT: func() RR { return new(OPT) },
|
||||
TypePTR: func() RR { return new(PTR) },
|
||||
|
@ -204,6 +206,7 @@ func (rr *HINFO) Header() *RR_Header { return &rr.Hdr }
|
|||
func (rr *HIP) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *HTTPS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *IPSECKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *ISDN) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *KEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *KX) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *L32) Header() *RR_Header { return &rr.Hdr }
|
||||
|
@ -227,6 +230,7 @@ func (rr *NSEC) Header() *RR_Header { return &rr.Hdr }
|
|||
func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NULL) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NXT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *OPT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *PTR) Header() *RR_Header { return &rr.Hdr }
|
||||
|
@ -437,6 +441,13 @@ func (rr *IPSECKEY) len(off int, compression map[string]struct{}) int {
|
|||
return l
|
||||
}
|
||||
|
||||
func (rr *ISDN) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Address) + 1
|
||||
l += len(rr.SubAddress) + 1
|
||||
return l
|
||||
}
|
||||
|
||||
func (rr *KX) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
|
@ -966,6 +977,10 @@ func (rr *IPSECKEY) copy() RR {
|
|||
}
|
||||
}
|
||||
|
||||
func (rr *ISDN) copy() RR {
|
||||
return &ISDN{rr.Hdr, rr.Address, rr.SubAddress}
|
||||
}
|
||||
|
||||
func (rr *KEY) copy() RR {
|
||||
return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||
}
|
||||
|
@ -1092,6 +1107,10 @@ func (rr *NULL) copy() RR {
|
|||
return &NULL{rr.Hdr, rr.Data}
|
||||
}
|
||||
|
||||
func (rr *NXT) copy() RR {
|
||||
return &NXT{*rr.NSEC.copy().(*NSEC)}
|
||||
}
|
||||
|
||||
func (rr *OPENPGPKEY) copy() RR {
|
||||
return &OPENPGPKEY{rr.Hdr, rr.PublicKey}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
bin/*
|
||||
.*.sw?
|
||||
.idea
|
||||
logs/*
|
||||
|
||||
# gg ignores
|
||||
vendor/src/*
|
||||
vendor/pkg/*
|
||||
servers.iml
|
||||
*.DS_Store
|
||||
|
||||
# vagrant ignores
|
||||
tools/vagrant/.vagrant
|
||||
tools/vagrant/adsrv-conf/.frontend
|
||||
tools/vagrant/adsrv-conf/.bidder
|
||||
tools/vagrant/adsrv-conf/.transcoder
|
||||
tools/vagrant/redis-cluster-conf/7777/nodes.conf
|
||||
tools/vagrant/redis-cluster-conf/7778/nodes.conf
|
||||
tools/vagrant/redis-cluster-conf/7779/nodes.conf
|
||||
*.aof
|
||||
*.rdb
|
||||
*.deb
|
|
@ -0,0 +1,24 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2024, Sudhi Herle
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,9 @@
|
|||
# go-sieve - SIEVE is simpler than LRU
|
||||
|
||||
## What is it?
|
||||
`go-sieve` is golang implementation of the [SIEVE](https://yazhuozhang.com/assets/pdf/nsdi24-sieve.pdf)
|
||||
cache eviction algorithm.
|
||||
|
||||
This implementation closely follows the paper's pseudo-code - but
|
||||
uses golang generics to provide an ergonomic interface.
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
// sieve.go - SIEVE - a simple and efficient cache
|
||||
//
|
||||
// (c) 2024 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Copyright 2024- Sudhi Herle <sw-at-herle-dot-net>
|
||||
// License: BSD-2-Clause
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
// This is golang implementation of the SIEVE cache eviction algorithm
|
||||
// The original paper is:
|
||||
// https://yazhuozhang.com/assets/pdf/nsdi24-sieve.pdf
|
||||
//
|
||||
// This implementation closely follows the paper - but uses golang generics
|
||||
// for an ergonomic interface.
|
||||
|
||||
// Package sieve implements the SIEVE cache eviction algorithm.
|
||||
// SIEVE stands in contrast to other eviction algorithms like LRU, 2Q, ARC
|
||||
// with its simplicity. The original paper is in:
|
||||
// https://yazhuozhang.com/assets/pdf/nsdi24-sieve.pdf
|
||||
//
|
||||
// SIEVE is built on a FIFO queue - with an extra pointer (called "hand") in
|
||||
// the paper. This "hand" plays a crucial role in determining who to evict
|
||||
// next.
|
||||
package sieve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// node contains the <key, val> tuple as a node in a linked list.
|
||||
type node[K comparable, V any] struct {
|
||||
sync.Mutex
|
||||
key K
|
||||
val V
|
||||
visited atomic.Bool
|
||||
next *node[K, V]
|
||||
prev *node[K, V]
|
||||
}
|
||||
|
||||
// Sieve represents a cache mapping the key of type 'K' with
|
||||
// a value of type 'V'. The type 'K' must implement the
|
||||
// comparable trait. An instance of Sieve has a fixed max capacity;
|
||||
// new additions to the cache beyond the capacity will cause cache
|
||||
// eviction of other entries - as determined by the SIEVE algorithm.
|
||||
type Sieve[K comparable, V any] struct {
|
||||
mu sync.Mutex
|
||||
cache *syncMap[K, *node[K, V]]
|
||||
head *node[K, V]
|
||||
tail *node[K, V]
|
||||
hand *node[K, V]
|
||||
size int
|
||||
capacity int
|
||||
|
||||
pool *syncPool[node[K, V]]
|
||||
}
|
||||
|
||||
// New creates a new cache of size 'capacity' mapping key 'K' to value 'V'
|
||||
func New[K comparable, V any](capacity int) *Sieve[K, V] {
|
||||
s := &Sieve[K, V]{
|
||||
cache: newSyncMap[K, *node[K, V]](),
|
||||
capacity: capacity,
|
||||
pool: newSyncPool[node[K, V]](),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Get fetches the value for a given key in the cache.
|
||||
// It returns true if the key is in the cache, false otherwise.
|
||||
// The zero value for 'V' is returned when key is not in the cache.
|
||||
func (s *Sieve[K, V]) Get(key K) (V, bool) {
|
||||
|
||||
if v, ok := s.cache.Get(key); ok {
|
||||
v.visited.Store(true)
|
||||
return v.val, true
|
||||
}
|
||||
|
||||
var x V
|
||||
return x, false
|
||||
}
|
||||
|
||||
// Add adds a new element to the cache or overwrite one if it exists
|
||||
// Return true if we replaced, false otherwise
|
||||
func (s *Sieve[K, V]) Add(key K, val V) bool {
|
||||
|
||||
if v, ok := s.cache.Get(key); ok {
|
||||
v.visited.Store(true)
|
||||
v.Lock()
|
||||
v.val = val
|
||||
v.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.add(key, val)
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
// Probe adds <key, val> if not present in the cache.
|
||||
// Returns:
|
||||
//
|
||||
// <cached-val, true> when key is present in the cache
|
||||
// <val, false> when key is not present in the cache
|
||||
func (s *Sieve[K, V]) Probe(key K, val V) (V, bool) {
|
||||
|
||||
if v, ok := s.cache.Get(key); ok {
|
||||
v.visited.Store(true)
|
||||
return v.val, true
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.add(key, val)
|
||||
s.mu.Unlock()
|
||||
return val, false
|
||||
}
|
||||
|
||||
// Delete deletes the named key from the cache
|
||||
// It returns true if the item was in the cache and false otherwise
|
||||
func (s *Sieve[K, V]) Delete(key K) bool {
|
||||
|
||||
if v, ok := s.cache.Del(key); ok {
|
||||
s.mu.Lock()
|
||||
s.remove(v)
|
||||
s.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Purge resets the cache
|
||||
func (s *Sieve[K, V]) Purge() {
|
||||
s.mu.Lock()
|
||||
s.cache = newSyncMap[K, *node[K, V]]()
|
||||
s.head = nil
|
||||
s.tail = nil
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Len returns the current cache utilization
|
||||
func (s *Sieve[K, V]) Len() int {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// Cap returns the max cache capacity
|
||||
func (s *Sieve[K, V]) Cap() int {
|
||||
return s.capacity
|
||||
}
|
||||
|
||||
// String returns a string description of the sieve cache
|
||||
func (s *Sieve[K, V]) String() string {
|
||||
s.mu.Lock()
|
||||
m := s.desc()
|
||||
s.mu.Unlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// Dump dumps all the cache contents as a newline delimited
|
||||
// string.
|
||||
func (s *Sieve[K, V]) Dump() string {
|
||||
var b strings.Builder
|
||||
|
||||
s.mu.Lock()
|
||||
b.WriteString(s.desc())
|
||||
b.WriteRune('\n')
|
||||
for n := s.head; n != nil; n = n.next {
|
||||
h := " "
|
||||
if n == s.hand {
|
||||
h = ">>"
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("%svisited=%v, key=%v, val=%v\n", h, n.visited.Load(), n.key, n.val))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// -- internal methods --
|
||||
|
||||
// add a new tuple to the cache and evict as necessary
|
||||
// caller must hold lock.
|
||||
func (s *Sieve[K, V]) add(key K, val V) {
|
||||
// cache miss; we evict and fnd a new node
|
||||
if s.size == s.capacity {
|
||||
s.evict()
|
||||
}
|
||||
|
||||
n := s.newNode(key, val)
|
||||
|
||||
// Eviction is guaranteed to remove one node; so this should never happen.
|
||||
if n == nil {
|
||||
msg := fmt.Sprintf("%T: add <%v>: objpool empty after eviction", s, key)
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
s.cache.Put(key, n)
|
||||
|
||||
// insert at the head of the list
|
||||
n.next = s.head
|
||||
n.prev = nil
|
||||
if s.head != nil {
|
||||
s.head.prev = n
|
||||
}
|
||||
s.head = n
|
||||
if s.tail == nil {
|
||||
s.tail = n
|
||||
}
|
||||
|
||||
s.size += 1
|
||||
}
|
||||
|
||||
// evict an item from the cache.
|
||||
// NB: Caller must hold the lock
|
||||
func (s *Sieve[K, V]) evict() {
|
||||
hand := s.hand
|
||||
if hand == nil {
|
||||
hand = s.tail
|
||||
}
|
||||
|
||||
for hand != nil {
|
||||
if !hand.visited.Load() {
|
||||
s.cache.Del(hand.key)
|
||||
s.remove(hand)
|
||||
s.hand = hand.prev
|
||||
return
|
||||
}
|
||||
hand.visited.Store(false)
|
||||
hand = hand.prev
|
||||
// wrap around and start again
|
||||
if hand == nil {
|
||||
hand = s.tail
|
||||
}
|
||||
}
|
||||
s.hand = hand
|
||||
}
|
||||
|
||||
func (s *Sieve[K, V]) remove(n *node[K, V]) {
|
||||
s.size -= 1
|
||||
|
||||
// remove node from list
|
||||
if n.prev != nil {
|
||||
n.prev.next = n.next
|
||||
} else {
|
||||
s.head = n.next
|
||||
}
|
||||
if n.next != nil {
|
||||
n.next.prev = n.prev
|
||||
} else {
|
||||
s.tail = n.prev
|
||||
}
|
||||
|
||||
s.pool.Put(n)
|
||||
}
|
||||
|
||||
func (s *Sieve[K, V]) newNode(key K, val V) *node[K, V] {
|
||||
n := s.pool.Get()
|
||||
n.key, n.val = key, val
|
||||
n.next, n.prev = nil, nil
|
||||
n.visited.Store(false)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// desc describes the properties of the sieve
|
||||
func (s *Sieve[K, V]) desc() string {
|
||||
m := fmt.Sprintf("cache<%T>: size %d, cap %d, head=%p, tail=%p, hand=%p",
|
||||
s, s.size, s.capacity, s.head, s.tail, s.hand)
|
||||
return m
|
||||
}
|
||||
|
||||
// Generic sync.Pool
|
||||
type syncPool[T any] struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func newSyncPool[T any]() *syncPool[T] {
|
||||
p := &syncPool[T]{
|
||||
pool: sync.Pool{
|
||||
New: func() any { return new(T) },
|
||||
},
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *syncPool[T]) Get() *T {
|
||||
p := s.pool.Get()
|
||||
return p.(*T)
|
||||
}
|
||||
|
||||
func (s *syncPool[T]) Put(n *T) {
|
||||
s.pool.Put(n)
|
||||
}
|
||||
|
||||
// generic sync.Map
|
||||
type syncMap[K comparable, V any] struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
func newSyncMap[K comparable, V any]() *syncMap[K, V] {
|
||||
m := syncMap[K, V]{}
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *syncMap[K, V]) Get(key K) (V, bool) {
|
||||
v, ok := m.m.Load(key)
|
||||
if ok {
|
||||
return v.(V), true
|
||||
}
|
||||
|
||||
var z V
|
||||
return z, false
|
||||
}
|
||||
|
||||
func (m *syncMap[K, V]) Put(key K, val V) {
|
||||
m.m.Store(key, val)
|
||||
}
|
||||
|
||||
func (m *syncMap[K, V]) Del(key K) (V, bool) {
|
||||
x, ok := m.m.LoadAndDelete(key)
|
||||
if ok {
|
||||
return x.(V), true
|
||||
}
|
||||
|
||||
var z V
|
||||
return z, false
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,6 +0,0 @@
|
|||
# qtls
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/quic-go/qtls-go1-20.svg)](https://pkg.go.dev/github.com/quic-go/qtls-go1-20)
|
||||
[![.github/workflows/go-test.yml](https://github.com/quic-go/qtls-go1-20/actions/workflows/go-test.yml/badge.svg)](https://github.com/quic-go/qtls-go1-20/actions/workflows/go-test.yml)
|
||||
|
||||
This repository contains a modified version of the standard library's TLS implementation, modified for the QUIC protocol. It is used by [quic-go](https://github.com/quic-go/quic-go).
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import "strconv"
|
||||
|
||||
// An AlertError is a TLS alert.
|
||||
//
|
||||
// When using a QUIC transport, QUICConn methods will return an error
|
||||
// which wraps AlertError rather than sending a TLS alert.
|
||||
type AlertError uint8
|
||||
|
||||
func (e AlertError) Error() string {
|
||||
return alert(e).String()
|
||||
}
|
||||
|
||||
type alert uint8
|
||||
|
||||
const (
|
||||
// alert level
|
||||
alertLevelWarning = 1
|
||||
alertLevelError = 2
|
||||
)
|
||||
|
||||
const (
|
||||
alertCloseNotify alert = 0
|
||||
alertUnexpectedMessage alert = 10
|
||||
alertBadRecordMAC alert = 20
|
||||
alertDecryptionFailed alert = 21
|
||||
alertRecordOverflow alert = 22
|
||||
alertDecompressionFailure alert = 30
|
||||
alertHandshakeFailure alert = 40
|
||||
alertBadCertificate alert = 42
|
||||
alertUnsupportedCertificate alert = 43
|
||||
alertCertificateRevoked alert = 44
|
||||
alertCertificateExpired alert = 45
|
||||
alertCertificateUnknown alert = 46
|
||||
alertIllegalParameter alert = 47
|
||||
alertUnknownCA alert = 48
|
||||
alertAccessDenied alert = 49
|
||||
alertDecodeError alert = 50
|
||||
alertDecryptError alert = 51
|
||||
alertExportRestriction alert = 60
|
||||
alertProtocolVersion alert = 70
|
||||
alertInsufficientSecurity alert = 71
|
||||
alertInternalError alert = 80
|
||||
alertInappropriateFallback alert = 86
|
||||
alertUserCanceled alert = 90
|
||||
alertNoRenegotiation alert = 100
|
||||
alertMissingExtension alert = 109
|
||||
alertUnsupportedExtension alert = 110
|
||||
alertCertificateUnobtainable alert = 111
|
||||
alertUnrecognizedName alert = 112
|
||||
alertBadCertificateStatusResponse alert = 113
|
||||
alertBadCertificateHashValue alert = 114
|
||||
alertUnknownPSKIdentity alert = 115
|
||||
alertCertificateRequired alert = 116
|
||||
alertNoApplicationProtocol alert = 120
|
||||
)
|
||||
|
||||
var alertText = map[alert]string{
|
||||
alertCloseNotify: "close notify",
|
||||
alertUnexpectedMessage: "unexpected message",
|
||||
alertBadRecordMAC: "bad record MAC",
|
||||
alertDecryptionFailed: "decryption failed",
|
||||
alertRecordOverflow: "record overflow",
|
||||
alertDecompressionFailure: "decompression failure",
|
||||
alertHandshakeFailure: "handshake failure",
|
||||
alertBadCertificate: "bad certificate",
|
||||
alertUnsupportedCertificate: "unsupported certificate",
|
||||
alertCertificateRevoked: "revoked certificate",
|
||||
alertCertificateExpired: "expired certificate",
|
||||
alertCertificateUnknown: "unknown certificate",
|
||||
alertIllegalParameter: "illegal parameter",
|
||||
alertUnknownCA: "unknown certificate authority",
|
||||
alertAccessDenied: "access denied",
|
||||
alertDecodeError: "error decoding message",
|
||||
alertDecryptError: "error decrypting message",
|
||||
alertExportRestriction: "export restriction",
|
||||
alertProtocolVersion: "protocol version not supported",
|
||||
alertInsufficientSecurity: "insufficient security level",
|
||||
alertInternalError: "internal error",
|
||||
alertInappropriateFallback: "inappropriate fallback",
|
||||
alertUserCanceled: "user canceled",
|
||||
alertNoRenegotiation: "no renegotiation",
|
||||
alertMissingExtension: "missing extension",
|
||||
alertUnsupportedExtension: "unsupported extension",
|
||||
alertCertificateUnobtainable: "certificate unobtainable",
|
||||
alertUnrecognizedName: "unrecognized name",
|
||||
alertBadCertificateStatusResponse: "bad certificate status response",
|
||||
alertBadCertificateHashValue: "bad certificate hash value",
|
||||
alertUnknownPSKIdentity: "unknown PSK identity",
|
||||
alertCertificateRequired: "certificate required",
|
||||
alertNoApplicationProtocol: "no application protocol",
|
||||
}
|
||||
|
||||
func (e alert) String() string {
|
||||
s, ok := alertText[e]
|
||||
if ok {
|
||||
return "tls: " + s
|
||||
}
|
||||
return "tls: alert(" + strconv.Itoa(int(e)) + ")"
|
||||
}
|
||||
|
||||
func (e alert) Error() string {
|
||||
return e.String()
|
||||
}
|
|
@ -1,293 +0,0 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// verifyHandshakeSignature verifies a signature against pre-hashed
|
||||
// (if required) handshake contents.
|
||||
func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, signed, sig []byte) error {
|
||||
switch sigType {
|
||||
case signatureECDSA:
|
||||
pubKey, ok := pubkey.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an ECDSA public key, got %T", pubkey)
|
||||
}
|
||||
if !ecdsa.VerifyASN1(pubKey, signed, sig) {
|
||||
return errors.New("ECDSA verification failure")
|
||||
}
|
||||
case signatureEd25519:
|
||||
pubKey, ok := pubkey.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an Ed25519 public key, got %T", pubkey)
|
||||
}
|
||||
if !ed25519.Verify(pubKey, signed, sig) {
|
||||
return errors.New("Ed25519 verification failure")
|
||||
}
|
||||
case signaturePKCS1v15:
|
||||
pubKey, ok := pubkey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an RSA public key, got %T", pubkey)
|
||||
}
|
||||
if err := rsa.VerifyPKCS1v15(pubKey, hashFunc, signed, sig); err != nil {
|
||||
return err
|
||||
}
|
||||
case signatureRSAPSS:
|
||||
pubKey, ok := pubkey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an RSA public key, got %T", pubkey)
|
||||
}
|
||||
signOpts := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}
|
||||
if err := rsa.VerifyPSS(pubKey, hashFunc, signed, sig, signOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("internal error: unknown signature type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serverSignatureContext = "TLS 1.3, server CertificateVerify\x00"
|
||||
clientSignatureContext = "TLS 1.3, client CertificateVerify\x00"
|
||||
)
|
||||
|
||||
var signaturePadding = []byte{
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
}
|
||||
|
||||
// signedMessage returns the pre-hashed (if necessary) message to be signed by
|
||||
// certificate keys in TLS 1.3. See RFC 8446, Section 4.4.3.
|
||||
func signedMessage(sigHash crypto.Hash, context string, transcript hash.Hash) []byte {
|
||||
if sigHash == directSigning {
|
||||
b := &bytes.Buffer{}
|
||||
b.Write(signaturePadding)
|
||||
io.WriteString(b, context)
|
||||
b.Write(transcript.Sum(nil))
|
||||
return b.Bytes()
|
||||
}
|
||||
h := sigHash.New()
|
||||
h.Write(signaturePadding)
|
||||
io.WriteString(h, context)
|
||||
h.Write(transcript.Sum(nil))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// typeAndHashFromSignatureScheme returns the corresponding signature type and
|
||||
// crypto.Hash for a given TLS SignatureScheme.
|
||||
func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType uint8, hash crypto.Hash, err error) {
|
||||
switch signatureAlgorithm {
|
||||
case PKCS1WithSHA1, PKCS1WithSHA256, PKCS1WithSHA384, PKCS1WithSHA512:
|
||||
sigType = signaturePKCS1v15
|
||||
case PSSWithSHA256, PSSWithSHA384, PSSWithSHA512:
|
||||
sigType = signatureRSAPSS
|
||||
case ECDSAWithSHA1, ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512:
|
||||
sigType = signatureECDSA
|
||||
case Ed25519:
|
||||
sigType = signatureEd25519
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
|
||||
}
|
||||
switch signatureAlgorithm {
|
||||
case PKCS1WithSHA1, ECDSAWithSHA1:
|
||||
hash = crypto.SHA1
|
||||
case PKCS1WithSHA256, PSSWithSHA256, ECDSAWithP256AndSHA256:
|
||||
hash = crypto.SHA256
|
||||
case PKCS1WithSHA384, PSSWithSHA384, ECDSAWithP384AndSHA384:
|
||||
hash = crypto.SHA384
|
||||
case PKCS1WithSHA512, PSSWithSHA512, ECDSAWithP521AndSHA512:
|
||||
hash = crypto.SHA512
|
||||
case Ed25519:
|
||||
hash = directSigning
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
|
||||
}
|
||||
return sigType, hash, nil
|
||||
}
|
||||
|
||||
// legacyTypeAndHashFromPublicKey returns the fixed signature type and crypto.Hash for
|
||||
// a given public key used with TLS 1.0 and 1.1, before the introduction of
|
||||
// signature algorithm negotiation.
|
||||
func legacyTypeAndHashFromPublicKey(pub crypto.PublicKey) (sigType uint8, hash crypto.Hash, err error) {
|
||||
switch pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return signaturePKCS1v15, crypto.MD5SHA1, nil
|
||||
case *ecdsa.PublicKey:
|
||||
return signatureECDSA, crypto.SHA1, nil
|
||||
case ed25519.PublicKey:
|
||||
// RFC 8422 specifies support for Ed25519 in TLS 1.0 and 1.1,
|
||||
// but it requires holding on to a handshake transcript to do a
|
||||
// full signature, and not even OpenSSL bothers with the
|
||||
// complexity, so we can't even test it properly.
|
||||
return 0, 0, fmt.Errorf("tls: Ed25519 public keys are not supported before TLS 1.2")
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("tls: unsupported public key: %T", pub)
|
||||
}
|
||||
}
|
||||
|
||||
var rsaSignatureSchemes = []struct {
|
||||
scheme SignatureScheme
|
||||
minModulusBytes int
|
||||
maxVersion uint16
|
||||
}{
|
||||
// RSA-PSS is used with PSSSaltLengthEqualsHash, and requires
|
||||
// emLen >= hLen + sLen + 2
|
||||
{PSSWithSHA256, crypto.SHA256.Size()*2 + 2, VersionTLS13},
|
||||
{PSSWithSHA384, crypto.SHA384.Size()*2 + 2, VersionTLS13},
|
||||
{PSSWithSHA512, crypto.SHA512.Size()*2 + 2, VersionTLS13},
|
||||
// PKCS #1 v1.5 uses prefixes from hashPrefixes in crypto/rsa, and requires
|
||||
// emLen >= len(prefix) + hLen + 11
|
||||
// TLS 1.3 dropped support for PKCS #1 v1.5 in favor of RSA-PSS.
|
||||
{PKCS1WithSHA256, 19 + crypto.SHA256.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA384, 19 + crypto.SHA384.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA512, 19 + crypto.SHA512.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA1, 15 + crypto.SHA1.Size() + 11, VersionTLS12},
|
||||
}
|
||||
|
||||
// signatureSchemesForCertificate returns the list of supported SignatureSchemes
|
||||
// for a given certificate, based on the public key and the protocol version,
|
||||
// and optionally filtered by its explicit SupportedSignatureAlgorithms.
|
||||
//
|
||||
// This function must be kept in sync with supportedSignatureAlgorithms.
|
||||
// FIPS filtering is applied in the caller, selectSignatureScheme.
|
||||
func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme {
|
||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var sigAlgs []SignatureScheme
|
||||
switch pub := priv.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
if version != VersionTLS13 {
|
||||
// In TLS 1.2 and earlier, ECDSA algorithms are not
|
||||
// constrained to a single curve.
|
||||
sigAlgs = []SignatureScheme{
|
||||
ECDSAWithP256AndSHA256,
|
||||
ECDSAWithP384AndSHA384,
|
||||
ECDSAWithP521AndSHA512,
|
||||
ECDSAWithSHA1,
|
||||
}
|
||||
break
|
||||
}
|
||||
switch pub.Curve {
|
||||
case elliptic.P256():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP256AndSHA256}
|
||||
case elliptic.P384():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP384AndSHA384}
|
||||
case elliptic.P521():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP521AndSHA512}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
case *rsa.PublicKey:
|
||||
size := pub.Size()
|
||||
sigAlgs = make([]SignatureScheme, 0, len(rsaSignatureSchemes))
|
||||
for _, candidate := range rsaSignatureSchemes {
|
||||
if size >= candidate.minModulusBytes && version <= candidate.maxVersion {
|
||||
sigAlgs = append(sigAlgs, candidate.scheme)
|
||||
}
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
sigAlgs = []SignatureScheme{Ed25519}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
var filteredSigAlgs []SignatureScheme
|
||||
for _, sigAlg := range sigAlgs {
|
||||
if isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) {
|
||||
filteredSigAlgs = append(filteredSigAlgs, sigAlg)
|
||||
}
|
||||
}
|
||||
return filteredSigAlgs
|
||||
}
|
||||
return sigAlgs
|
||||
}
|
||||
|
||||
// selectSignatureScheme picks a SignatureScheme from the peer's preference list
|
||||
// that works with the selected certificate. It's only called for protocol
|
||||
// versions that support signature algorithms, so TLS 1.2 and 1.3.
|
||||
func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureScheme) (SignatureScheme, error) {
|
||||
supportedAlgs := signatureSchemesForCertificate(vers, c)
|
||||
if len(supportedAlgs) == 0 {
|
||||
return 0, unsupportedCertificateError(c)
|
||||
}
|
||||
if len(peerAlgs) == 0 && vers == VersionTLS12 {
|
||||
// For TLS 1.2, if the client didn't send signature_algorithms then we
|
||||
// can assume that it supports SHA1. See RFC 5246, Section 7.4.1.4.1.
|
||||
peerAlgs = []SignatureScheme{PKCS1WithSHA1, ECDSAWithSHA1}
|
||||
}
|
||||
// Pick signature scheme in the peer's preference order, as our
|
||||
// preference order is not configurable.
|
||||
for _, preferredAlg := range peerAlgs {
|
||||
if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, fipsSupportedSignatureAlgorithms) {
|
||||
continue
|
||||
}
|
||||
if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) {
|
||||
return preferredAlg, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("tls: peer doesn't support any of the certificate's signature algorithms")
|
||||
}
|
||||
|
||||
// unsupportedCertificateError returns a helpful error for certificates with
|
||||
// an unsupported private key.
|
||||
func unsupportedCertificateError(cert *Certificate) error {
|
||||
switch cert.PrivateKey.(type) {
|
||||
case rsa.PrivateKey, ecdsa.PrivateKey:
|
||||
return fmt.Errorf("tls: unsupported certificate: private key is %T, expected *%T",
|
||||
cert.PrivateKey, cert.PrivateKey)
|
||||
case *ed25519.PrivateKey:
|
||||
return fmt.Errorf("tls: unsupported certificate: private key is *ed25519.PrivateKey, expected ed25519.PrivateKey")
|
||||
}
|
||||
|
||||
signer, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return fmt.Errorf("tls: certificate private key (%T) does not implement crypto.Signer",
|
||||
cert.PrivateKey)
|
||||
}
|
||||
|
||||
switch pub := signer.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
switch pub.Curve {
|
||||
case elliptic.P256():
|
||||
case elliptic.P384():
|
||||
case elliptic.P521():
|
||||
default:
|
||||
return fmt.Errorf("tls: unsupported certificate curve (%s)", pub.Curve.Params().Name)
|
||||
}
|
||||
case *rsa.PublicKey:
|
||||
return fmt.Errorf("tls: certificate RSA key size too small for supported signature algorithms")
|
||||
case ed25519.PublicKey:
|
||||
default:
|
||||
return fmt.Errorf("tls: unsupported certificate key (%T)", pub)
|
||||
}
|
||||
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
return fmt.Errorf("tls: peer doesn't support the certificate custom signature algorithms")
|
||||
}
|
||||
|
||||
return fmt.Errorf("tls: internal error: unsupported key (%T)", cert.PrivateKey)
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
refs atomic.Int64
|
||||
cert *x509.Certificate
|
||||
}
|
||||
|
||||
// certCache implements an intern table for reference counted x509.Certificates,
|
||||
// implemented in a similar fashion to BoringSSL's CRYPTO_BUFFER_POOL. This
|
||||
// allows for a single x509.Certificate to be kept in memory and referenced from
|
||||
// multiple Conns. Returned references should not be mutated by callers. Certificates
|
||||
// are still safe to use after they are removed from the cache.
|
||||
//
|
||||
// Certificates are returned wrapped in a activeCert struct that should be held by
|
||||
// the caller. When references to the activeCert are freed, the number of references
|
||||
// to the certificate in the cache is decremented. Once the number of references
|
||||
// reaches zero, the entry is evicted from the cache.
|
||||
//
|
||||
// The main difference between this implementation and CRYPTO_BUFFER_POOL is that
|
||||
// CRYPTO_BUFFER_POOL is a more generic structure which supports blobs of data,
|
||||
// rather than specific structures. Since we only care about x509.Certificates,
|
||||
// certCache is implemented as a specific cache, rather than a generic one.
|
||||
//
|
||||
// See https://boringssl.googlesource.com/boringssl/+/master/include/openssl/pool.h
|
||||
// and https://boringssl.googlesource.com/boringssl/+/master/crypto/pool/pool.c
|
||||
// for the BoringSSL reference.
|
||||
type certCache struct {
|
||||
sync.Map
|
||||
}
|
||||
|
||||
var clientCertCache = new(certCache)
|
||||
|
||||
// activeCert is a handle to a certificate held in the cache. Once there are
|
||||
// no alive activeCerts for a given certificate, the certificate is removed
|
||||
// from the cache by a finalizer.
|
||||
type activeCert struct {
|
||||
cert *x509.Certificate
|
||||
}
|
||||
|
||||
// active increments the number of references to the entry, wraps the
|
||||
// certificate in the entry in a activeCert, and sets the finalizer.
|
||||
//
|
||||
// Note that there is a race between active and the finalizer set on the
|
||||
// returned activeCert, triggered if active is called after the ref count is
|
||||
// decremented such that refs may be > 0 when evict is called. We consider this
|
||||
// safe, since the caller holding an activeCert for an entry that is no longer
|
||||
// in the cache is fine, with the only side effect being the memory overhead of
|
||||
// there being more than one distinct reference to a certificate alive at once.
|
||||
func (cc *certCache) active(e *cacheEntry) *activeCert {
|
||||
e.refs.Add(1)
|
||||
a := &activeCert{e.cert}
|
||||
runtime.SetFinalizer(a, func(_ *activeCert) {
|
||||
if e.refs.Add(-1) == 0 {
|
||||
cc.evict(e)
|
||||
}
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// evict removes a cacheEntry from the cache.
|
||||
func (cc *certCache) evict(e *cacheEntry) {
|
||||
cc.Delete(string(e.cert.Raw))
|
||||
}
|
||||
|
||||
// newCert returns a x509.Certificate parsed from der. If there is already a copy
|
||||
// of the certificate in the cache, a reference to the existing certificate will
|
||||
// be returned. Otherwise, a fresh certificate will be added to the cache, and
|
||||
// the reference returned. The returned reference should not be mutated.
|
||||
func (cc *certCache) newCert(der []byte) (*activeCert, error) {
|
||||
if entry, ok := cc.Load(string(der)); ok {
|
||||
return cc.active(entry.(*cacheEntry)), nil
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := &cacheEntry{cert: cert}
|
||||
if entry, loaded := cc.LoadOrStore(string(der), entry); loaded {
|
||||
return cc.active(entry.(*cacheEntry)), nil
|
||||
}
|
||||
return cc.active(entry), nil
|
||||
}
|
|
@ -1,691 +0,0 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/hmac"
|
||||
"crypto/rc4"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"hash"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// CipherSuite is a TLS cipher suite. Note that most functions in this package
|
||||
// accept and expose cipher suite IDs instead of this type.
|
||||
type CipherSuite struct {
|
||||
ID uint16
|
||||
Name string
|
||||
|
||||
// Supported versions is the list of TLS protocol versions that can
|
||||
// negotiate this cipher suite.
|
||||
SupportedVersions []uint16
|
||||
|
||||
// Insecure is true if the cipher suite has known security issues
|
||||
// due to its primitives, design, or implementation.
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
var (
|
||||
supportedUpToTLS12 = []uint16{VersionTLS10, VersionTLS11, VersionTLS12}
|
||||
supportedOnlyTLS12 = []uint16{VersionTLS12}
|
||||
supportedOnlyTLS13 = []uint16{VersionTLS13}
|
||||
)
|
||||
|
||||
// CipherSuites returns a list of cipher suites currently implemented by this
|
||||
// package, excluding those with security issues, which are returned by
|
||||
// InsecureCipherSuites.
|
||||
//
|
||||
// The list is sorted by ID. Note that the default cipher suites selected by
|
||||
// this package might depend on logic that can't be captured by a static list,
|
||||
// and might not match those returned by this function.
|
||||
func CipherSuites() []*CipherSuite {
|
||||
return []*CipherSuite{
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||
|
||||
{TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
|
||||
{TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
|
||||
{TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
|
||||
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||
}
|
||||
}
|
||||
|
||||
// InsecureCipherSuites returns a list of cipher suites currently implemented by
|
||||
// this package and which have security issues.
|
||||
//
|
||||
// Most applications should not use the cipher suites in this list, and should
|
||||
// only use those returned by CipherSuites.
|
||||
func InsecureCipherSuites() []*CipherSuite {
|
||||
// This list includes RC4, CBC_SHA256, and 3DES cipher suites. See
|
||||
// cipherSuitesPreferenceOrder for details.
|
||||
return []*CipherSuite{
|
||||
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
}
|
||||
}
|
||||
|
||||
// CipherSuiteName returns the standard name for the passed cipher suite ID
|
||||
// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or a fallback representation
|
||||
// of the ID value if the cipher suite is not implemented by this package.
|
||||
func CipherSuiteName(id uint16) string {
|
||||
for _, c := range CipherSuites() {
|
||||
if c.ID == id {
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
for _, c := range InsecureCipherSuites() {
|
||||
if c.ID == id {
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("0x%04X", id)
|
||||
}
|
||||
|
||||
const (
|
||||
// suiteECDHE indicates that the cipher suite involves elliptic curve
|
||||
// Diffie-Hellman. This means that it should only be selected when the
|
||||
// client indicates that it supports ECC with a curve and point format
|
||||
// that we're happy with.
|
||||
suiteECDHE = 1 << iota
|
||||
// suiteECSign indicates that the cipher suite involves an ECDSA or
|
||||
// EdDSA signature and therefore may only be selected when the server's
|
||||
// certificate is ECDSA or EdDSA. If this is not set then the cipher suite
|
||||
// is RSA based.
|
||||
suiteECSign
|
||||
// suiteTLS12 indicates that the cipher suite should only be advertised
|
||||
// and accepted when using TLS 1.2.
|
||||
suiteTLS12
|
||||
// suiteSHA384 indicates that the cipher suite uses SHA384 as the
|
||||
// handshake hash.
|
||||
suiteSHA384
|
||||
)
|
||||
|
||||
// A cipherSuite is a TLS 1.0–1.2 cipher suite, and defines the key exchange
|
||||
// mechanism, as well as the cipher+MAC pair or the AEAD.
|
||||
type cipherSuite struct {
|
||||
id uint16
|
||||
// the lengths, in bytes, of the key material needed for each component.
|
||||
keyLen int
|
||||
macLen int
|
||||
ivLen int
|
||||
ka func(version uint16) keyAgreement
|
||||
// flags is a bitmask of the suite* values, above.
|
||||
flags int
|
||||
cipher func(key, iv []byte, isRead bool) any
|
||||
mac func(key []byte) hash.Hash
|
||||
aead func(key, fixedNonce []byte) aead
|
||||
}
|
||||
|
||||
var cipherSuites = []*cipherSuite{ // TODO: replace with a map, since the order doesn't matter.
|
||||
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 32, 0, 12, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 32, 0, 12, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheRSAKA, suiteECDHE | suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherAES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, 0, cipher3DES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE, cipherRC4, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherRC4, macSHA1, nil},
|
||||
}
|
||||
|
||||
// selectCipherSuite returns the first TLS 1.0–1.2 cipher suite from ids which
|
||||
// is also in supportedIDs and passes the ok filter.
|
||||
func selectCipherSuite(ids, supportedIDs []uint16, ok func(*cipherSuite) bool) *cipherSuite {
|
||||
for _, id := range ids {
|
||||
candidate := cipherSuiteByID(id)
|
||||
if candidate == nil || !ok(candidate) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, suppID := range supportedIDs {
|
||||
if id == suppID {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A cipherSuiteTLS13 defines only the pair of the AEAD algorithm and hash
|
||||
// algorithm to be used with HKDF. See RFC 8446, Appendix B.4.
|
||||
type cipherSuiteTLS13 struct {
|
||||
id uint16
|
||||
keyLen int
|
||||
aead func(key, fixedNonce []byte) aead
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
var cipherSuitesTLS13 = []*cipherSuiteTLS13{ // TODO: replace with a map.
|
||||
{TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256},
|
||||
{TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256},
|
||||
{TLS_AES_256_GCM_SHA384, 32, aeadAESGCMTLS13, crypto.SHA384},
|
||||
}
|
||||
|
||||
// cipherSuitesPreferenceOrder is the order in which we'll select (on the
|
||||
// server) or advertise (on the client) TLS 1.0–1.2 cipher suites.
|
||||
//
|
||||
// Cipher suites are filtered but not reordered based on the application and
|
||||
// peer's preferences, meaning we'll never select a suite lower in this list if
|
||||
// any higher one is available. This makes it more defensible to keep weaker
|
||||
// cipher suites enabled, especially on the server side where we get the last
|
||||
// word, since there are no known downgrade attacks on cipher suites selection.
|
||||
//
|
||||
// The list is sorted by applying the following priority rules, stopping at the
|
||||
// first (most important) applicable one:
|
||||
//
|
||||
// - Anything else comes before RC4
|
||||
//
|
||||
// RC4 has practically exploitable biases. See https://www.rc4nomore.com.
|
||||
//
|
||||
// - Anything else comes before CBC_SHA256
|
||||
//
|
||||
// SHA-256 variants of the CBC ciphersuites don't implement any Lucky13
|
||||
// countermeasures. See http://www.isg.rhul.ac.uk/tls/Lucky13.html and
|
||||
// https://www.imperialviolet.org/2013/02/04/luckythirteen.html.
|
||||
//
|
||||
// - Anything else comes before 3DES
|
||||
//
|
||||
// 3DES has 64-bit blocks, which makes it fundamentally susceptible to
|
||||
// birthday attacks. See https://sweet32.info.
|
||||
//
|
||||
// - ECDHE comes before anything else
|
||||
//
|
||||
// Once we got the broken stuff out of the way, the most important
|
||||
// property a cipher suite can have is forward secrecy. We don't
|
||||
// implement FFDHE, so that means ECDHE.
|
||||
//
|
||||
// - AEADs come before CBC ciphers
|
||||
//
|
||||
// Even with Lucky13 countermeasures, MAC-then-Encrypt CBC cipher suites
|
||||
// are fundamentally fragile, and suffered from an endless sequence of
|
||||
// padding oracle attacks. See https://eprint.iacr.org/2015/1129,
|
||||
// https://www.imperialviolet.org/2014/12/08/poodleagain.html, and
|
||||
// https://blog.cloudflare.com/yet-another-padding-oracle-in-openssl-cbc-ciphersuites/.
|
||||
//
|
||||
// - AES comes before ChaCha20
|
||||
//
|
||||
// When AES hardware is available, AES-128-GCM and AES-256-GCM are faster
|
||||
// than ChaCha20Poly1305.
|
||||
//
|
||||
// When AES hardware is not available, AES-128-GCM is one or more of: much
|
||||
// slower, way more complex, and less safe (because not constant time)
|
||||
// than ChaCha20Poly1305.
|
||||
//
|
||||
// We use this list if we think both peers have AES hardware, and
|
||||
// cipherSuitesPreferenceOrderNoAES otherwise.
|
||||
//
|
||||
// - AES-128 comes before AES-256
|
||||
//
|
||||
// The only potential advantages of AES-256 are better multi-target
|
||||
// margins, and hypothetical post-quantum properties. Neither apply to
|
||||
// TLS, and AES-256 is slower due to its four extra rounds (which don't
|
||||
// contribute to the advantages above).
|
||||
//
|
||||
// - ECDSA comes before RSA
|
||||
//
|
||||
// The relative order of ECDSA and RSA cipher suites doesn't matter,
|
||||
// as they depend on the certificate. Pick one to get a stable order.
|
||||
var cipherSuitesPreferenceOrder = []uint16{
|
||||
// AEADs w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
|
||||
// CBC w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
|
||||
// AEADs w/o ECDHE
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
|
||||
// CBC w/o ECDHE
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
|
||||
// 3DES
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
|
||||
// CBC_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
|
||||
// RC4
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
TLS_RSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
|
||||
var cipherSuitesPreferenceOrderNoAES = []uint16{
|
||||
// ChaCha20Poly1305
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
|
||||
// AES-GCM w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
|
||||
// The rest of cipherSuitesPreferenceOrder.
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
TLS_RSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
|
||||
// disabledCipherSuites are not used unless explicitly listed in
|
||||
// Config.CipherSuites. They MUST be at the end of cipherSuitesPreferenceOrder.
|
||||
var disabledCipherSuites = []uint16{
|
||||
// CBC_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
|
||||
// RC4
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
TLS_RSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCipherSuitesLen = len(cipherSuitesPreferenceOrder) - len(disabledCipherSuites)
|
||||
defaultCipherSuites = cipherSuitesPreferenceOrder[:defaultCipherSuitesLen]
|
||||
)
|
||||
|
||||
// defaultCipherSuitesTLS13 is also the preference order, since there are no
|
||||
// disabled by default TLS 1.3 cipher suites. The same AES vs ChaCha20 logic as
|
||||
// cipherSuitesPreferenceOrder applies.
|
||||
var defaultCipherSuitesTLS13 = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
var defaultCipherSuitesTLS13NoAES = []uint16{
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
||||
)
|
||||
|
||||
var aesgcmCiphers = map[uint16]bool{
|
||||
// TLS 1.2
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: true,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: true,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: true,
|
||||
// TLS 1.3
|
||||
TLS_AES_128_GCM_SHA256: true,
|
||||
TLS_AES_256_GCM_SHA384: true,
|
||||
}
|
||||
|
||||
var nonAESGCMAEADCiphers = map[uint16]bool{
|
||||
// TLS 1.2
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: true,
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: true,
|
||||
// TLS 1.3
|
||||
TLS_CHACHA20_POLY1305_SHA256: true,
|
||||
}
|
||||
|
||||
// aesgcmPreferred returns whether the first known cipher in the preference list
|
||||
// is an AES-GCM cipher, implying the peer has hardware support for it.
|
||||
func aesgcmPreferred(ciphers []uint16) bool {
|
||||
for _, cID := range ciphers {
|
||||
if c := cipherSuiteByID(cID); c != nil {
|
||||
return aesgcmCiphers[cID]
|
||||
}
|
||||
if c := cipherSuiteTLS13ByID(cID); c != nil {
|
||||
return aesgcmCiphers[cID]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cipherRC4(key, iv []byte, isRead bool) any {
|
||||
cipher, _ := rc4.NewCipher(key)
|
||||
return cipher
|
||||
}
|
||||
|
||||
func cipher3DES(key, iv []byte, isRead bool) any {
|
||||
block, _ := des.NewTripleDESCipher(key)
|
||||
if isRead {
|
||||
return cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
return cipher.NewCBCEncrypter(block, iv)
|
||||
}
|
||||
|
||||
func cipherAES(key, iv []byte, isRead bool) any {
|
||||
block, _ := aes.NewCipher(key)
|
||||
if isRead {
|
||||
return cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
return cipher.NewCBCEncrypter(block, iv)
|
||||
}
|
||||
|
||||
// macSHA1 returns a SHA-1 based constant time MAC.
|
||||
func macSHA1(key []byte) hash.Hash {
|
||||
h := sha1.New
|
||||
h = newConstantTimeHash(h)
|
||||
return hmac.New(h, key)
|
||||
}
|
||||
|
||||
// macSHA256 returns a SHA-256 based MAC. This is only supported in TLS 1.2 and
|
||||
// is currently only used in disabled-by-default cipher suites.
|
||||
func macSHA256(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}
|
||||
|
||||
type aead interface {
|
||||
cipher.AEAD
|
||||
|
||||
// explicitNonceLen returns the number of bytes of explicit nonce
|
||||
// included in each record. This is eight for older AEADs and
|
||||
// zero for modern ones.
|
||||
explicitNonceLen() int
|
||||
}
|
||||
|
||||
const (
|
||||
aeadNonceLength = 12
|
||||
noncePrefixLength = 4
|
||||
)
|
||||
|
||||
// prefixNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to
|
||||
// each call.
|
||||
type prefixNonceAEAD struct {
|
||||
// nonce contains the fixed part of the nonce in the first four bytes.
|
||||
nonce [aeadNonceLength]byte
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
func (f *prefixNonceAEAD) NonceSize() int { return aeadNonceLength - noncePrefixLength }
|
||||
func (f *prefixNonceAEAD) Overhead() int { return f.aead.Overhead() }
|
||||
func (f *prefixNonceAEAD) explicitNonceLen() int { return f.NonceSize() }
|
||||
|
||||
func (f *prefixNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
|
||||
copy(f.nonce[4:], nonce)
|
||||
return f.aead.Seal(out, f.nonce[:], plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (f *prefixNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
copy(f.nonce[4:], nonce)
|
||||
return f.aead.Open(out, f.nonce[:], ciphertext, additionalData)
|
||||
}
|
||||
|
||||
// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce
|
||||
// before each call.
|
||||
type xorNonceAEAD struct {
|
||||
nonceMask [aeadNonceLength]byte
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
|
||||
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
|
||||
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
|
||||
|
||||
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func aeadAESGCM(key, noncePrefix []byte) aead {
|
||||
if len(noncePrefix) != noncePrefixLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var aead cipher.AEAD
|
||||
aead, err = cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &prefixNonceAEAD{aead: aead}
|
||||
copy(ret.nonce[:], noncePrefix)
|
||||
return ret
|
||||
}
|
||||
|
||||
func aeadAESGCMTLS13(key, nonceMask []byte) aead {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
aead, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &xorNonceAEAD{aead: aead}
|
||||
copy(ret.nonceMask[:], nonceMask)
|
||||
return ret
|
||||
}
|
||||
|
||||
func aeadChaCha20Poly1305(key, nonceMask []byte) aead {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &xorNonceAEAD{aead: aead}
|
||||
copy(ret.nonceMask[:], nonceMask)
|
||||
return ret
|
||||
}
|
||||
|
||||
type constantTimeHash interface {
|
||||
hash.Hash
|
||||
ConstantTimeSum(b []byte) []byte
|
||||
}
|
||||
|
||||
// cthWrapper wraps any hash.Hash that implements ConstantTimeSum, and replaces
|
||||
// with that all calls to Sum. It's used to obtain a ConstantTimeSum-based HMAC.
|
||||
type cthWrapper struct {
|
||||
h constantTimeHash
|
||||
}
|
||||
|
||||
func (c *cthWrapper) Size() int { return c.h.Size() }
|
||||
func (c *cthWrapper) BlockSize() int { return c.h.BlockSize() }
|
||||
func (c *cthWrapper) Reset() { c.h.Reset() }
|
||||
func (c *cthWrapper) Write(p []byte) (int, error) { return c.h.Write(p) }
|
||||
func (c *cthWrapper) Sum(b []byte) []byte { return c.h.ConstantTimeSum(b) }
|
||||
|
||||
func newConstantTimeHash(h func() hash.Hash) func() hash.Hash {
|
||||
return func() hash.Hash {
|
||||
return &cthWrapper{h().(constantTimeHash)}
|
||||
}
|
||||
}
|
||||
|
||||
// tls10MAC implements the TLS 1.0 MAC function. RFC 2246, Section 6.2.3.
|
||||
func tls10MAC(h hash.Hash, out, seq, header, data, extra []byte) []byte {
|
||||
h.Reset()
|
||||
h.Write(seq)
|
||||
h.Write(header)
|
||||
h.Write(data)
|
||||
res := h.Sum(out)
|
||||
if extra != nil {
|
||||
h.Write(extra)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func rsaKA(version uint16) keyAgreement {
|
||||
return rsaKeyAgreement{}
|
||||
}
|
||||
|
||||
func ecdheECDSAKA(version uint16) keyAgreement {
|
||||
return &ecdheKeyAgreement{
|
||||
isRSA: false,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
func ecdheRSAKA(version uint16) keyAgreement {
|
||||
return &ecdheKeyAgreement{
|
||||
isRSA: true,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
// mutualCipherSuite returns a cipherSuite given a list of supported
|
||||
// ciphersuites and the id requested by the peer.
|
||||
func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
return cipherSuiteByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteByID(id uint16) *cipherSuite {
|
||||
for _, cipherSuite := range cipherSuites {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mutualCipherSuiteTLS13(have []uint16, want uint16) *cipherSuiteTLS13 {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
return cipherSuiteTLS13ByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 {
|
||||
for _, cipherSuite := range cipherSuitesTLS13 {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A list of cipher suite IDs that are, or have been, implemented by this
|
||||
// package.
|
||||
//
|
||||
// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
|
||||
const (
|
||||
// TLS 1.0 - 1.2 cipher suites.
|
||||
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
|
||||
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
|
||||
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca8
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
|
||||
|
||||
// TLS 1.3 cipher suites.
|
||||
TLS_AES_128_GCM_SHA256 uint16 = 0x1301
|
||||
TLS_AES_256_GCM_SHA384 uint16 = 0x1302
|
||||
TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303
|
||||
|
||||
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
|
||||
// that the client is doing version fallback. See RFC 7507.
|
||||
TLS_FALLBACK_SCSV uint16 = 0x5600
|
||||
|
||||
// Legacy names for the corresponding cipher suites with the correct _SHA256
|
||||
// suffix, retained for backward compatibility.
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,782 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/hmac"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
type clientHandshakeStateTLS13 struct {
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
serverHello *serverHelloMsg
|
||||
hello *clientHelloMsg
|
||||
ecdheKey *ecdh.PrivateKey
|
||||
|
||||
session *clientSessionState
|
||||
earlySecret []byte
|
||||
binderKey []byte
|
||||
|
||||
certReq *certificateRequestMsgTLS13
|
||||
usingPSK bool
|
||||
sentDummyCCS bool
|
||||
suite *cipherSuiteTLS13
|
||||
transcript hash.Hash
|
||||
masterSecret []byte
|
||||
trafficSecret []byte // client_application_traffic_secret_0
|
||||
}
|
||||
|
||||
// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheKey, and,
|
||||
// optionally, hs.session, hs.earlySecret and hs.binderKey to be set.
|
||||
func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
if needFIPS() {
|
||||
return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
|
||||
}
|
||||
|
||||
// The server must not select TLS 1.3 in a renegotiation. See RFC 8446,
|
||||
// sections 4.1.2 and 4.1.3.
|
||||
if c.handshakes > 0 {
|
||||
c.sendAlert(alertProtocolVersion)
|
||||
return errors.New("tls: server selected TLS 1.3 in a renegotiation")
|
||||
}
|
||||
|
||||
// Consistency check on the presence of a keyShare and its parameters.
|
||||
if hs.ecdheKey == nil || len(hs.hello.keyShares) != 1 {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
if err := hs.checkServerHelloOrHRR(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.transcript = hs.suite.hash.New()
|
||||
|
||||
if err := transcriptMsg(hs.hello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
|
||||
if err := hs.sendDummyChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.processHelloRetryRequest(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.buffering = true
|
||||
if err := hs.processServerHello(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendDummyChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.establishHandshakeKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readServerParameters(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readServerCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readServerFinished(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendClientCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendClientFinished(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isHandshakeComplete.Store(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkServerHelloOrHRR does validity checks that apply to both ServerHello and
|
||||
// HelloRetryRequest messages. It sets hs.suite.
|
||||
func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error {
|
||||
c := hs.c
|
||||
|
||||
if hs.serverHello.supportedVersion == 0 {
|
||||
c.sendAlert(alertMissingExtension)
|
||||
return errors.New("tls: server selected TLS 1.3 using the legacy version field")
|
||||
}
|
||||
|
||||
if hs.serverHello.supportedVersion != VersionTLS13 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected an invalid version after a HelloRetryRequest")
|
||||
}
|
||||
|
||||
if hs.serverHello.vers != VersionTLS12 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an incorrect legacy version")
|
||||
}
|
||||
|
||||
if hs.serverHello.ocspStapling ||
|
||||
hs.serverHello.ticketSupported ||
|
||||
hs.serverHello.secureRenegotiationSupported ||
|
||||
len(hs.serverHello.secureRenegotiation) != 0 ||
|
||||
len(hs.serverHello.alpnProtocol) != 0 ||
|
||||
len(hs.serverHello.scts) != 0 {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: server sent a ServerHello extension forbidden in TLS 1.3")
|
||||
}
|
||||
|
||||
if !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server did not echo the legacy session ID")
|
||||
}
|
||||
|
||||
if hs.serverHello.compressionMethod != compressionNone {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported compression format")
|
||||
}
|
||||
|
||||
selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite)
|
||||
if hs.suite != nil && selectedSuite != hs.suite {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server changed cipher suite after a HelloRetryRequest")
|
||||
}
|
||||
if selectedSuite == nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server chose an unconfigured cipher suite")
|
||||
}
|
||||
hs.suite = selectedSuite
|
||||
c.cipherSuite = hs.suite.id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDummyChangeCipherSpec sends a ChangeCipherSpec record for compatibility
|
||||
// with middleboxes that didn't implement TLS correctly. See RFC 8446, Appendix D.4.
|
||||
func (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
|
||||
if hs.c.quic != nil {
|
||||
return nil
|
||||
}
|
||||
if hs.sentDummyCCS {
|
||||
return nil
|
||||
}
|
||||
hs.sentDummyCCS = true
|
||||
|
||||
return hs.c.writeChangeCipherRecord()
|
||||
}
|
||||
|
||||
// processHelloRetryRequest handles the HRR in hs.serverHello, modifies and
|
||||
// resends hs.hello, and reads the new ServerHello into hs.serverHello.
|
||||
func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
c := hs.c
|
||||
|
||||
// The first ClientHello gets double-hashed into the transcript upon a
|
||||
// HelloRetryRequest. (The idea is that the server might offload transcript
|
||||
// storage to the client in the cookie.) See RFC 8446, Section 4.4.1.
|
||||
chHash := hs.transcript.Sum(nil)
|
||||
hs.transcript.Reset()
|
||||
hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
hs.transcript.Write(chHash)
|
||||
if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The only HelloRetryRequest extensions we support are key_share and
|
||||
// cookie, and clients must abort the handshake if the HRR would not result
|
||||
// in any change in the ClientHello.
|
||||
if hs.serverHello.selectedGroup == 0 && hs.serverHello.cookie == nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest message")
|
||||
}
|
||||
|
||||
if hs.serverHello.cookie != nil {
|
||||
hs.hello.cookie = hs.serverHello.cookie
|
||||
}
|
||||
|
||||
if hs.serverHello.serverShare.group != 0 {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: received malformed key_share extension")
|
||||
}
|
||||
|
||||
// If the server sent a key_share extension selecting a group, ensure it's
|
||||
// a group we advertised but did not send a key share for, and send a key
|
||||
// share for it this time.
|
||||
if curveID := hs.serverHello.selectedGroup; curveID != 0 {
|
||||
curveOK := false
|
||||
for _, id := range hs.hello.supportedCurves {
|
||||
if id == curveID {
|
||||
curveOK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !curveOK {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); sentID == curveID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
|
||||
}
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
key, err := generateECDHEKey(c.config.rand(), curveID)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
hs.ecdheKey = key
|
||||
hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
|
||||
}
|
||||
|
||||
hs.hello.raw = nil
|
||||
if len(hs.hello.pskIdentities) > 0 {
|
||||
pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
|
||||
if pskSuite == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if pskSuite.hash == hs.suite.hash {
|
||||
// Update binders and obfuscated_ticket_age.
|
||||
ticketAge := uint32(c.config.time().Sub(hs.session.receivedAt) / time.Millisecond)
|
||||
hs.hello.pskIdentities[0].obfuscatedTicketAge = ticketAge + hs.session.ageAdd
|
||||
|
||||
transcript := hs.suite.hash.New()
|
||||
transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
transcript.Write(chHash)
|
||||
if err := transcriptMsg(hs.serverHello, transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
helloBytes, err := hs.hello.marshalWithoutBinders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transcript.Write(helloBytes)
|
||||
pskBinders := [][]byte{hs.suite.finishedHash(hs.binderKey, transcript)}
|
||||
if err := hs.hello.updateBinders(pskBinders); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Server selected a cipher suite incompatible with the PSK.
|
||||
hs.hello.pskIdentities = nil
|
||||
hs.hello.pskBinders = nil
|
||||
}
|
||||
}
|
||||
|
||||
if hs.hello.earlyData {
|
||||
hs.hello.earlyData = false
|
||||
c.quicRejectedEarlyData()
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serverHelloMsg is not included in the transcript
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverHello, ok := msg.(*serverHelloMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(serverHello, msg)
|
||||
}
|
||||
hs.serverHello = serverHello
|
||||
|
||||
if err := hs.checkServerHelloOrHRR(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
||||
c := hs.c
|
||||
|
||||
if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return errors.New("tls: server sent two HelloRetryRequest messages")
|
||||
}
|
||||
|
||||
if len(hs.serverHello.cookie) != 0 {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: server sent a cookie in a normal ServerHello")
|
||||
}
|
||||
|
||||
if hs.serverHello.selectedGroup != 0 {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: malformed key_share extension")
|
||||
}
|
||||
|
||||
if hs.serverHello.serverShare.group == 0 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server did not send a key share")
|
||||
}
|
||||
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); hs.serverHello.serverShare.group != sentID {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
|
||||
if !hs.serverHello.selectedIdentityPresent {
|
||||
return nil
|
||||
}
|
||||
|
||||
if int(hs.serverHello.selectedIdentity) >= len(hs.hello.pskIdentities) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected an invalid PSK")
|
||||
}
|
||||
|
||||
if len(hs.hello.pskIdentities) != 1 || hs.session == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
|
||||
if pskSuite == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if pskSuite.hash != hs.suite.hash {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected an invalid PSK and cipher suite pair")
|
||||
}
|
||||
|
||||
hs.usingPSK = true
|
||||
c.didResume = true
|
||||
c.peerCertificates = hs.session.serverCertificates
|
||||
c.verifiedChains = hs.session.verifiedChains
|
||||
c.ocspResponse = hs.session.ocspResponse
|
||||
c.scts = hs.session.scts
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
c := hs.c
|
||||
|
||||
peerKey, err := hs.ecdheKey.Curve().NewPublicKey(hs.serverHello.serverShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
sharedKey, err := hs.ecdheKey.ECDH(peerKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
|
||||
earlySecret := hs.earlySecret
|
||||
if !hs.usingPSK {
|
||||
earlySecret = hs.suite.extract(nil, nil)
|
||||
}
|
||||
|
||||
handshakeSecret := hs.suite.extract(sharedKey,
|
||||
hs.suite.deriveSecret(earlySecret, "derived", nil))
|
||||
|
||||
clientSecret := hs.suite.deriveSecret(handshakeSecret,
|
||||
clientHandshakeTrafficLabel, hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
|
||||
serverSecret := hs.suite.deriveSecret(handshakeSecret,
|
||||
serverHandshakeTrafficLabel, hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
if c.hand.Len() != 0 {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelHandshake, hs.suite.id, clientSecret)
|
||||
c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, serverSecret)
|
||||
}
|
||||
|
||||
err = c.config.writeKeyLog(keyLogLabelClientHandshake, hs.hello.random, clientSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
err = c.config.writeKeyLog(keyLogLabelServerHandshake, hs.hello.random, serverSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
hs.masterSecret = hs.suite.extract(nil,
|
||||
hs.suite.deriveSecret(handshakeSecret, "derived", nil))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) readServerParameters() error {
|
||||
c := hs.c
|
||||
|
||||
msg, err := c.readHandshake(hs.transcript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedExtensions, ok := msg.(*encryptedExtensionsMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(encryptedExtensions, msg)
|
||||
}
|
||||
|
||||
if err := checkALPN(hs.hello.alpnProtocols, encryptedExtensions.alpnProtocol, c.quic != nil); err != nil {
|
||||
// RFC 8446 specifies that no_application_protocol is sent by servers, but
|
||||
// does not specify how clients handle the selection of an incompatible protocol.
|
||||
// RFC 9001 Section 8.1 specifies that QUIC clients send no_application_protocol
|
||||
// in this case. Always sending no_application_protocol seems reasonable.
|
||||
c.sendAlert(alertNoApplicationProtocol)
|
||||
return err
|
||||
}
|
||||
c.clientProtocol = encryptedExtensions.alpnProtocol
|
||||
|
||||
if c.quic != nil {
|
||||
if encryptedExtensions.quicTransportParameters == nil {
|
||||
// RFC 9001 Section 8.2.
|
||||
c.sendAlert(alertMissingExtension)
|
||||
return errors.New("tls: server did not send a quic_transport_parameters extension")
|
||||
}
|
||||
c.quicSetTransportParameters(encryptedExtensions.quicTransportParameters)
|
||||
} else {
|
||||
if encryptedExtensions.quicTransportParameters != nil {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: server sent an unexpected quic_transport_parameters extension")
|
||||
}
|
||||
}
|
||||
|
||||
if hs.hello.earlyData && !encryptedExtensions.earlyData {
|
||||
c.quicRejectedEarlyData()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
// Either a PSK or a certificate is always used, but not both.
|
||||
// See RFC 8446, Section 4.1.1.
|
||||
if hs.usingPSK {
|
||||
// Make sure the connection is still being verified whether or not this
|
||||
// is a resumption. Resumptions currently don't reverify certificates so
|
||||
// they don't call verifyServerCertificate. See Issue 31641.
|
||||
if c.config.VerifyConnection != nil {
|
||||
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
msg, err := c.readHandshake(hs.transcript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certReq, ok := msg.(*certificateRequestMsgTLS13)
|
||||
if ok {
|
||||
hs.certReq = certReq
|
||||
|
||||
msg, err = c.readHandshake(hs.transcript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
certMsg, ok := msg.(*certificateMsgTLS13)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certMsg, msg)
|
||||
}
|
||||
if len(certMsg.certificate.Certificate) == 0 {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: received empty certificates message")
|
||||
}
|
||||
|
||||
c.scts = certMsg.certificate.SignedCertificateTimestamps
|
||||
c.ocspResponse = certMsg.certificate.OCSPStaple
|
||||
|
||||
if err := c.verifyServerCertificate(certMsg.certificate.Certificate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// certificateVerifyMsg is included in the transcript, but not until
|
||||
// after we verify the handshake signature, since the state before
|
||||
// this message was sent is used.
|
||||
msg, err = c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certVerify, ok := msg.(*certificateVerifyMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certVerify, msg)
|
||||
}
|
||||
|
||||
// See RFC 8446, Section 4.4.3.
|
||||
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: certificate used with invalid signature algorithm")
|
||||
}
|
||||
sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: certificate used with invalid signature algorithm")
|
||||
}
|
||||
signed := signedMessage(sigHash, serverSignatureContext, hs.transcript)
|
||||
if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,
|
||||
sigHash, signed, certVerify.signature); err != nil {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid signature by the server certificate: " + err.Error())
|
||||
}
|
||||
|
||||
if err := transcriptMsg(certVerify, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) readServerFinished() error {
|
||||
c := hs.c
|
||||
|
||||
// finishedMsg is included in the transcript, but not until after we
|
||||
// check the client version, since the state before this message was
|
||||
// sent is used during verification.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finished, ok := msg.(*finishedMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(finished, msg)
|
||||
}
|
||||
|
||||
expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
|
||||
if !hmac.Equal(expectedMAC, finished.verifyData) {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid server finished hash")
|
||||
}
|
||||
|
||||
if err := transcriptMsg(finished, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Derive secrets that take context through the server Finished.
|
||||
|
||||
hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
clientApplicationTrafficLabel, hs.transcript)
|
||||
serverSecret := hs.suite.deriveSecret(hs.masterSecret,
|
||||
serverApplicationTrafficLabel, hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
|
||||
|
||||
err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
err = c.config.writeKeyLog(keyLogLabelServerTraffic, hs.hello.random, serverSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
c.ekm = hs.suite.exportKeyingMaterial(hs.masterSecret, hs.transcript)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
if hs.certReq == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, err := c.getClientCertificate(toCertificateRequestInfo(&certificateRequestInfo{
|
||||
AcceptableCAs: hs.certReq.certificateAuthorities,
|
||||
SignatureSchemes: hs.certReq.supportedSignatureAlgorithms,
|
||||
Version: c.vers,
|
||||
ctx: hs.ctx,
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certMsg := new(certificateMsgTLS13)
|
||||
|
||||
certMsg.certificate = *cert
|
||||
certMsg.scts = hs.certReq.scts && len(cert.SignedCertificateTimestamps) > 0
|
||||
certMsg.ocspStapling = hs.certReq.ocspStapling && len(cert.OCSPStaple) > 0
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(certMsg, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we sent an empty certificate message, skip the CertificateVerify.
|
||||
if len(cert.Certificate) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
certVerifyMsg := new(certificateVerifyMsg)
|
||||
certVerifyMsg.hasSignatureAlgorithm = true
|
||||
|
||||
certVerifyMsg.signatureAlgorithm, err = selectSignatureScheme(c.vers, cert, hs.certReq.supportedSignatureAlgorithms)
|
||||
if err != nil {
|
||||
// getClientCertificate returned a certificate incompatible with the
|
||||
// CertificateRequestInfo supported signature algorithms.
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerifyMsg.signatureAlgorithm)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
signed := signedMessage(sigHash, clientSignatureContext, hs.transcript)
|
||||
signOpts := crypto.SignerOpts(sigHash)
|
||||
if sigType == signatureRSAPSS {
|
||||
signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
|
||||
}
|
||||
sig, err := cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: failed to sign handshake: " + err.Error())
|
||||
}
|
||||
certVerifyMsg.signature = sig
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(certVerifyMsg, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
|
||||
c := hs.c
|
||||
|
||||
finished := &finishedMsg{
|
||||
verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(finished, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
|
||||
|
||||
if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
|
||||
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
resumptionLabel, hs.transcript)
|
||||
}
|
||||
|
||||
if c.quic != nil {
|
||||
if c.hand.Len() != 0 {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelApplication, hs.suite.id, hs.trafficSecret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
||||
if !c.isClient {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return errors.New("tls: received new session ticket from a client")
|
||||
}
|
||||
|
||||
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// See RFC 8446, Section 4.6.1.
|
||||
if msg.lifetime == 0 {
|
||||
return nil
|
||||
}
|
||||
lifetime := time.Duration(msg.lifetime) * time.Second
|
||||
if lifetime > maxSessionTicketLifetime {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: received a session ticket with invalid lifetime")
|
||||
}
|
||||
|
||||
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||
if cipherSuite == nil || c.resumptionSecret == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
// We need to save the max_early_data_size that the server sent us, in order
|
||||
// to decide if we're going to try 0-RTT with this ticket.
|
||||
// However, at the same time, the qtls.ClientSessionTicket needs to be equal to
|
||||
// the tls.ClientSessionTicket, so we can't just add a new field to the struct.
|
||||
// We therefore abuse the nonce field (which is a byte slice)
|
||||
nonceWithEarlyData := make([]byte, len(msg.nonce)+4)
|
||||
binary.BigEndian.PutUint32(nonceWithEarlyData, msg.maxEarlyData)
|
||||
copy(nonceWithEarlyData[4:], msg.nonce)
|
||||
|
||||
var appData []byte
|
||||
if c.extraConfig != nil && c.extraConfig.GetAppDataForSessionState != nil {
|
||||
appData = c.extraConfig.GetAppDataForSessionState()
|
||||
}
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(clientSessionStateVersion) // revision
|
||||
b.AddUint32(msg.maxEarlyData)
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(appData)
|
||||
})
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(msg.nonce)
|
||||
})
|
||||
|
||||
// Save the resumption_master_secret and nonce instead of deriving the PSK
|
||||
// to do the least amount of work on NewSessionTicket messages before we
|
||||
// know if the ticket will be used. Forward secrecy of resumed connections
|
||||
// is guaranteed by the requirement for pskModeDHE.
|
||||
session := &clientSessionState{
|
||||
sessionTicket: msg.label,
|
||||
vers: c.vers,
|
||||
cipherSuite: c.cipherSuite,
|
||||
masterSecret: c.resumptionSecret,
|
||||
serverCertificates: c.peerCertificates,
|
||||
verifiedChains: c.verifiedChains,
|
||||
receivedAt: c.config.time(),
|
||||
nonce: b.BytesOrPanic(),
|
||||
useBy: c.config.time().Add(lifetime),
|
||||
ageAdd: msg.ageAdd,
|
||||
ocspResponse: c.ocspResponse,
|
||||
scts: c.scts,
|
||||
}
|
||||
|
||||
cacheKey := c.clientSessionCacheKey()
|
||||
if cacheKey != "" {
|
||||
c.config.ClientSessionCache.Put(cacheKey, toClientSessionState(session))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,899 +0,0 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// serverHandshakeState contains details of a server handshake in progress.
|
||||
// It's discarded once the handshake has completed.
|
||||
type serverHandshakeState struct {
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
clientHello *clientHelloMsg
|
||||
hello *serverHelloMsg
|
||||
suite *cipherSuite
|
||||
ecdheOk bool
|
||||
ecSignOk bool
|
||||
rsaDecryptOk bool
|
||||
rsaSignOk bool
|
||||
sessionState *sessionState
|
||||
finishedHash finishedHash
|
||||
masterSecret []byte
|
||||
cert *Certificate
|
||||
}
|
||||
|
||||
// serverHandshake performs a TLS handshake as a server.
|
||||
func (c *Conn) serverHandshake(ctx context.Context) error {
|
||||
clientHello, err := c.readClientHello(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.vers == VersionTLS13 {
|
||||
hs := serverHandshakeStateTLS13{
|
||||
c: c,
|
||||
ctx: ctx,
|
||||
clientHello: clientHello,
|
||||
}
|
||||
return hs.handshake()
|
||||
}
|
||||
|
||||
hs := serverHandshakeState{
|
||||
c: c,
|
||||
ctx: ctx,
|
||||
clientHello: clientHello,
|
||||
}
|
||||
return hs.handshake()
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
if err := hs.processClientHello(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For an overview of TLS handshaking, see RFC 5246, Section 7.3.
|
||||
c.buffering = true
|
||||
if hs.checkForResumption() {
|
||||
// The client has included a session ticket and so we do an abbreviated handshake.
|
||||
c.didResume = true
|
||||
if err := hs.doResumeHandshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.establishKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendSessionTicket(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendFinished(c.serverFinished[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.clientFinishedIsFirst = false
|
||||
if err := hs.readFinished(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// The client didn't include a session ticket, or it wasn't
|
||||
// valid so we do a full handshake.
|
||||
if err := hs.pickCipherSuite(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.doFullHandshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.establishKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readFinished(c.clientFinished[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
c.clientFinishedIsFirst = true
|
||||
c.buffering = true
|
||||
if err := hs.sendSessionTicket(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendFinished(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random)
|
||||
c.isHandshakeComplete.Store(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readClientHello reads a ClientHello message and selects the protocol version.
|
||||
func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
|
||||
// clientHelloMsg is included in the transcript, but we haven't initialized
|
||||
// it yet. The respective handshake functions will record it themselves.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientHello, ok := msg.(*clientHelloMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return nil, unexpectedMessageError(clientHello, msg)
|
||||
}
|
||||
|
||||
var configForClient *config
|
||||
originalConfig := c.config
|
||||
if c.config.GetConfigForClient != nil {
|
||||
chi := newClientHelloInfo(ctx, c, clientHello)
|
||||
if cfc, err := c.config.GetConfigForClient(chi); err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, err
|
||||
} else if cfc != nil {
|
||||
configForClient = fromConfig(cfc)
|
||||
c.config = configForClient
|
||||
}
|
||||
}
|
||||
c.ticketKeys = originalConfig.ticketKeys(configForClient)
|
||||
|
||||
clientVersions := clientHello.supportedVersions
|
||||
if len(clientHello.supportedVersions) == 0 {
|
||||
clientVersions = supportedVersionsFromMax(clientHello.vers)
|
||||
}
|
||||
c.vers, ok = c.config.mutualVersion(roleServer, clientVersions)
|
||||
if !ok {
|
||||
c.sendAlert(alertProtocolVersion)
|
||||
return nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
|
||||
}
|
||||
c.haveVers = true
|
||||
c.in.version = c.vers
|
||||
c.out.version = c.vers
|
||||
|
||||
return clientHello, nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) processClientHello() error {
|
||||
c := hs.c
|
||||
|
||||
hs.hello = new(serverHelloMsg)
|
||||
hs.hello.vers = c.vers
|
||||
|
||||
foundCompression := false
|
||||
// We only support null compression, so check that the client offered it.
|
||||
for _, compression := range hs.clientHello.compressionMethods {
|
||||
if compression == compressionNone {
|
||||
foundCompression = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundCompression {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: client does not support uncompressed connections")
|
||||
}
|
||||
|
||||
hs.hello.random = make([]byte, 32)
|
||||
serverRandom := hs.hello.random
|
||||
// Downgrade protection canaries. See RFC 8446, Section 4.1.3.
|
||||
maxVers := c.config.maxSupportedVersion(roleServer)
|
||||
if maxVers >= VersionTLS12 && c.vers < maxVers || testingOnlyForceDowngradeCanary {
|
||||
if c.vers == VersionTLS12 {
|
||||
copy(serverRandom[24:], downgradeCanaryTLS12)
|
||||
} else {
|
||||
copy(serverRandom[24:], downgradeCanaryTLS11)
|
||||
}
|
||||
serverRandom = serverRandom[:24]
|
||||
}
|
||||
_, err := io.ReadFull(c.config.rand(), serverRandom)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hs.clientHello.secureRenegotiation) != 0 {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: initial handshake had non-empty renegotiation extension")
|
||||
}
|
||||
|
||||
hs.hello.secureRenegotiationSupported = hs.clientHello.secureRenegotiationSupported
|
||||
hs.hello.compressionMethod = compressionNone
|
||||
if len(hs.clientHello.serverName) > 0 {
|
||||
c.serverName = hs.clientHello.serverName
|
||||
}
|
||||
|
||||
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, false)
|
||||
if err != nil {
|
||||
c.sendAlert(alertNoApplicationProtocol)
|
||||
return err
|
||||
}
|
||||
hs.hello.alpnProtocol = selectedProto
|
||||
c.clientProtocol = selectedProto
|
||||
|
||||
hs.cert, err = c.config.getCertificate(newClientHelloInfo(hs.ctx, c, hs.clientHello))
|
||||
if err != nil {
|
||||
if err == errNoCertificates {
|
||||
c.sendAlert(alertUnrecognizedName)
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if hs.clientHello.scts {
|
||||
hs.hello.scts = hs.cert.SignedCertificateTimestamps
|
||||
}
|
||||
|
||||
hs.ecdheOk = supportsECDHE(c.config, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints)
|
||||
|
||||
if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 {
|
||||
// Although omitting the ec_point_formats extension is permitted, some
|
||||
// old OpenSSL version will refuse to handshake if not present.
|
||||
//
|
||||
// Per RFC 4492, section 5.1.2, implementations MUST support the
|
||||
// uncompressed point format. See golang.org/issue/31943.
|
||||
hs.hello.supportedPoints = []uint8{pointFormatUncompressed}
|
||||
}
|
||||
|
||||
if priv, ok := hs.cert.PrivateKey.(crypto.Signer); ok {
|
||||
switch priv.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
hs.ecSignOk = true
|
||||
case ed25519.PublicKey:
|
||||
hs.ecSignOk = true
|
||||
case *rsa.PublicKey:
|
||||
hs.rsaSignOk = true
|
||||
default:
|
||||
c.sendAlert(alertInternalError)
|
||||
return fmt.Errorf("tls: unsupported signing key type (%T)", priv.Public())
|
||||
}
|
||||
}
|
||||
if priv, ok := hs.cert.PrivateKey.(crypto.Decrypter); ok {
|
||||
switch priv.Public().(type) {
|
||||
case *rsa.PublicKey:
|
||||
hs.rsaDecryptOk = true
|
||||
default:
|
||||
c.sendAlert(alertInternalError)
|
||||
return fmt.Errorf("tls: unsupported decryption key type (%T)", priv.Public())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// negotiateALPN picks a shared ALPN protocol that both sides support in server
|
||||
// preference order. If ALPN is not configured or the peer doesn't support it,
|
||||
// it returns "" and no error.
|
||||
func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, error) {
|
||||
if len(serverProtos) == 0 || len(clientProtos) == 0 {
|
||||
if quic && len(serverProtos) != 0 {
|
||||
// RFC 9001, Section 8.1
|
||||
return "", fmt.Errorf("tls: client did not request an application protocol")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
var http11fallback bool
|
||||
for _, s := range serverProtos {
|
||||
for _, c := range clientProtos {
|
||||
if s == c {
|
||||
return s, nil
|
||||
}
|
||||
if s == "h2" && c == "http/1.1" {
|
||||
http11fallback = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// As a special case, let http/1.1 clients connect to h2 servers as if they
|
||||
// didn't support ALPN. We used not to enforce protocol overlap, so over
|
||||
// time a number of HTTP servers were configured with only "h2", but
|
||||
// expected to accept connections from "http/1.1" clients. See Issue 46310.
|
||||
if http11fallback {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("tls: client requested unsupported application protocols (%s)", clientProtos)
|
||||
}
|
||||
|
||||
// supportsECDHE returns whether ECDHE key exchanges can be used with this
|
||||
// pre-TLS 1.3 client.
|
||||
func supportsECDHE(c *config, supportedCurves []CurveID, supportedPoints []uint8) bool {
|
||||
supportsCurve := false
|
||||
for _, curve := range supportedCurves {
|
||||
if c.supportsCurve(curve) {
|
||||
supportsCurve = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
supportsPointFormat := false
|
||||
for _, pointFormat := range supportedPoints {
|
||||
if pointFormat == pointFormatUncompressed {
|
||||
supportsPointFormat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Per RFC 8422, Section 5.1.2, if the Supported Point Formats extension is
|
||||
// missing, uncompressed points are supported. If supportedPoints is empty,
|
||||
// the extension must be missing, as an empty extension body is rejected by
|
||||
// the parser. See https://go.dev/issue/49126.
|
||||
if len(supportedPoints) == 0 {
|
||||
supportsPointFormat = true
|
||||
}
|
||||
|
||||
return supportsCurve && supportsPointFormat
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) pickCipherSuite() error {
|
||||
c := hs.c
|
||||
|
||||
preferenceOrder := cipherSuitesPreferenceOrder
|
||||
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
|
||||
preferenceOrder = cipherSuitesPreferenceOrderNoAES
|
||||
}
|
||||
|
||||
configCipherSuites := c.config.cipherSuites()
|
||||
preferenceList := make([]uint16, 0, len(configCipherSuites))
|
||||
for _, suiteID := range preferenceOrder {
|
||||
for _, id := range configCipherSuites {
|
||||
if id == suiteID {
|
||||
preferenceList = append(preferenceList, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk)
|
||||
if hs.suite == nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no cipher suite supported by both client and server")
|
||||
}
|
||||
c.cipherSuite = hs.suite.id
|
||||
|
||||
for _, id := range hs.clientHello.cipherSuites {
|
||||
if id == TLS_FALLBACK_SCSV {
|
||||
// The client is doing a fallback connection. See RFC 7507.
|
||||
if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer) {
|
||||
c.sendAlert(alertInappropriateFallback)
|
||||
return errors.New("tls: client using inappropriate protocol fallback")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) cipherSuiteOk(c *cipherSuite) bool {
|
||||
if c.flags&suiteECDHE != 0 {
|
||||
if !hs.ecdheOk {
|
||||
return false
|
||||
}
|
||||
if c.flags&suiteECSign != 0 {
|
||||
if !hs.ecSignOk {
|
||||
return false
|
||||
}
|
||||
} else if !hs.rsaSignOk {
|
||||
return false
|
||||
}
|
||||
} else if !hs.rsaDecryptOk {
|
||||
return false
|
||||
}
|
||||
if hs.c.vers < VersionTLS12 && c.flags&suiteTLS12 != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkForResumption reports whether we should perform resumption on this connection.
|
||||
func (hs *serverHandshakeState) checkForResumption() bool {
|
||||
c := hs.c
|
||||
|
||||
if c.config.SessionTicketsDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
plaintext, usedOldKey := c.decryptTicket(hs.clientHello.sessionTicket)
|
||||
if plaintext == nil {
|
||||
return false
|
||||
}
|
||||
hs.sessionState = &sessionState{usedOldKey: usedOldKey}
|
||||
ok := hs.sessionState.unmarshal(plaintext)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
createdAt := time.Unix(int64(hs.sessionState.createdAt), 0)
|
||||
if c.config.time().Sub(createdAt) > maxSessionTicketLifetime {
|
||||
return false
|
||||
}
|
||||
|
||||
// Never resume a session for a different TLS version.
|
||||
if c.vers != hs.sessionState.vers {
|
||||
return false
|
||||
}
|
||||
|
||||
cipherSuiteOk := false
|
||||
// Check that the client is still offering the ciphersuite in the session.
|
||||
for _, id := range hs.clientHello.cipherSuites {
|
||||
if id == hs.sessionState.cipherSuite {
|
||||
cipherSuiteOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !cipherSuiteOk {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check that we also support the ciphersuite from the session.
|
||||
hs.suite = selectCipherSuite([]uint16{hs.sessionState.cipherSuite},
|
||||
c.config.cipherSuites(), hs.cipherSuiteOk)
|
||||
if hs.suite == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
sessionHasClientCerts := len(hs.sessionState.certificates) != 0
|
||||
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
||||
if needClientCerts && !sessionHasClientCerts {
|
||||
return false
|
||||
}
|
||||
if sessionHasClientCerts && c.config.ClientAuth == NoClientCert {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) doResumeHandshake() error {
|
||||
c := hs.c
|
||||
|
||||
hs.hello.cipherSuite = hs.suite.id
|
||||
c.cipherSuite = hs.suite.id
|
||||
// We echo the client's session ID in the ServerHello to let it know
|
||||
// that we're doing a resumption.
|
||||
hs.hello.sessionId = hs.clientHello.sessionId
|
||||
hs.hello.ticketSupported = hs.sessionState.usedOldKey
|
||||
hs.finishedHash = newFinishedHash(c.vers, hs.suite)
|
||||
hs.finishedHash.discardHandshakeBuffer()
|
||||
if err := transcriptMsg(hs.clientHello, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.processCertsFromClient(Certificate{
|
||||
Certificate: hs.sessionState.certificates,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.config.VerifyConnection != nil {
|
||||
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hs.masterSecret = hs.sessionState.masterSecret
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) doFullHandshake() error {
|
||||
c := hs.c
|
||||
|
||||
if hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0 {
|
||||
hs.hello.ocspStapling = true
|
||||
}
|
||||
|
||||
hs.hello.ticketSupported = hs.clientHello.ticketSupported && !c.config.SessionTicketsDisabled
|
||||
hs.hello.cipherSuite = hs.suite.id
|
||||
|
||||
hs.finishedHash = newFinishedHash(hs.c.vers, hs.suite)
|
||||
if c.config.ClientAuth == NoClientCert {
|
||||
// No need to keep a full record of the handshake if client
|
||||
// certificates won't be used.
|
||||
hs.finishedHash.discardHandshakeBuffer()
|
||||
}
|
||||
if err := transcriptMsg(hs.clientHello, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certMsg := new(certificateMsg)
|
||||
certMsg.certificates = hs.cert.Certificate
|
||||
if _, err := hs.c.writeHandshakeRecord(certMsg, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hs.hello.ocspStapling {
|
||||
certStatus := new(certificateStatusMsg)
|
||||
certStatus.response = hs.cert.OCSPStaple
|
||||
if _, err := hs.c.writeHandshakeRecord(certStatus, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
keyAgreement := hs.suite.ka(c.vers)
|
||||
skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.cert, hs.clientHello, hs.hello)
|
||||
if err != nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return err
|
||||
}
|
||||
if skx != nil {
|
||||
if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var certReq *certificateRequestMsg
|
||||
if c.config.ClientAuth >= RequestClientCert {
|
||||
// Request a client certificate
|
||||
certReq = new(certificateRequestMsg)
|
||||
certReq.certificateTypes = []byte{
|
||||
byte(certTypeRSASign),
|
||||
byte(certTypeECDSASign),
|
||||
}
|
||||
if c.vers >= VersionTLS12 {
|
||||
certReq.hasSignatureAlgorithm = true
|
||||
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
|
||||
}
|
||||
|
||||
// An empty list of certificateAuthorities signals to
|
||||
// the client that it may send any certificate in response
|
||||
// to our request. When we know the CAs we trust, then
|
||||
// we can send them down, so that the client can choose
|
||||
// an appropriate certificate to give to us.
|
||||
if c.config.ClientCAs != nil {
|
||||
certReq.certificateAuthorities = c.config.ClientCAs.Subjects()
|
||||
}
|
||||
if _, err := hs.c.writeHandshakeRecord(certReq, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
helloDone := new(serverHelloDoneMsg)
|
||||
if _, err := hs.c.writeHandshakeRecord(helloDone, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pub crypto.PublicKey // public key for client auth, if any
|
||||
|
||||
msg, err := c.readHandshake(&hs.finishedHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we requested a client certificate, then the client must send a
|
||||
// certificate message, even if it's empty.
|
||||
if c.config.ClientAuth >= RequestClientCert {
|
||||
certMsg, ok := msg.(*certificateMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certMsg, msg)
|
||||
}
|
||||
|
||||
if err := c.processCertsFromClient(Certificate{
|
||||
Certificate: certMsg.certificates,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(certMsg.certificates) != 0 {
|
||||
pub = c.peerCertificates[0].PublicKey
|
||||
}
|
||||
|
||||
msg, err = c.readHandshake(&hs.finishedHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.config.VerifyConnection != nil {
|
||||
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Get client key exchange
|
||||
ckx, ok := msg.(*clientKeyExchangeMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(ckx, msg)
|
||||
}
|
||||
|
||||
preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers)
|
||||
if err != nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return err
|
||||
}
|
||||
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
|
||||
if err := c.config.writeKeyLog(keyLogLabelTLS12, hs.clientHello.random, hs.masterSecret); err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
// If we received a client cert in response to our certificate request message,
|
||||
// the client will send us a certificateVerifyMsg immediately after the
|
||||
// clientKeyExchangeMsg. This message is a digest of all preceding
|
||||
// handshake-layer messages that is signed using the private key corresponding
|
||||
// to the client's certificate. This allows us to verify that the client is in
|
||||
// possession of the private key of the certificate.
|
||||
if len(c.peerCertificates) > 0 {
|
||||
// certificateVerifyMsg is included in the transcript, but not until
|
||||
// after we verify the handshake signature, since the state before
|
||||
// this message was sent is used.
|
||||
msg, err = c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certVerify, ok := msg.(*certificateVerifyMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certVerify, msg)
|
||||
}
|
||||
|
||||
var sigType uint8
|
||||
var sigHash crypto.Hash
|
||||
if c.vers >= VersionTLS12 {
|
||||
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, certReq.supportedSignatureAlgorithms) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client certificate used with invalid signature algorithm")
|
||||
}
|
||||
sigType, sigHash, err = typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
} else {
|
||||
sigType, sigHash, err = legacyTypeAndHashFromPublicKey(pub)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash)
|
||||
if err := verifyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid signature by the client certificate: " + err.Error())
|
||||
}
|
||||
|
||||
if err := transcriptMsg(certVerify, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hs.finishedHash.discardHandshakeBuffer()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) establishKeys() error {
|
||||
c := hs.c
|
||||
|
||||
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
|
||||
keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen)
|
||||
|
||||
var clientCipher, serverCipher any
|
||||
var clientHash, serverHash hash.Hash
|
||||
|
||||
if hs.suite.aead == nil {
|
||||
clientCipher = hs.suite.cipher(clientKey, clientIV, true /* for reading */)
|
||||
clientHash = hs.suite.mac(clientMAC)
|
||||
serverCipher = hs.suite.cipher(serverKey, serverIV, false /* not for reading */)
|
||||
serverHash = hs.suite.mac(serverMAC)
|
||||
} else {
|
||||
clientCipher = hs.suite.aead(clientKey, clientIV)
|
||||
serverCipher = hs.suite.aead(serverKey, serverIV)
|
||||
}
|
||||
|
||||
c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
|
||||
c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) readFinished(out []byte) error {
|
||||
c := hs.c
|
||||
|
||||
if err := c.readChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// finishedMsg is included in the transcript, but not until after we
|
||||
// check the client version, since the state before this message was
|
||||
// sent is used during verification.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientFinished, ok := msg.(*finishedMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(clientFinished, msg)
|
||||
}
|
||||
|
||||
verify := hs.finishedHash.clientSum(hs.masterSecret)
|
||||
if len(verify) != len(clientFinished.verifyData) ||
|
||||
subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: client's Finished message is incorrect")
|
||||
}
|
||||
|
||||
if err := transcriptMsg(clientFinished, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(out, verify)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) sendSessionTicket() error {
|
||||
// ticketSupported is set in a resumption handshake if the
|
||||
// ticket from the client was encrypted with an old session
|
||||
// ticket key and thus a refreshed ticket should be sent.
|
||||
if !hs.hello.ticketSupported {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := hs.c
|
||||
m := new(newSessionTicketMsg)
|
||||
|
||||
createdAt := uint64(c.config.time().Unix())
|
||||
if hs.sessionState != nil {
|
||||
// If this is re-wrapping an old key, then keep
|
||||
// the original time it was created.
|
||||
createdAt = hs.sessionState.createdAt
|
||||
}
|
||||
|
||||
var certsFromClient [][]byte
|
||||
for _, cert := range c.peerCertificates {
|
||||
certsFromClient = append(certsFromClient, cert.Raw)
|
||||
}
|
||||
state := sessionState{
|
||||
vers: c.vers,
|
||||
cipherSuite: hs.suite.id,
|
||||
createdAt: createdAt,
|
||||
masterSecret: hs.masterSecret,
|
||||
certificates: certsFromClient,
|
||||
}
|
||||
stateBytes, err := state.marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.ticket, err = c.encryptTicket(stateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(m, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeState) sendFinished(out []byte) error {
|
||||
c := hs.c
|
||||
|
||||
if err := c.writeChangeCipherRecord(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finished := new(finishedMsg)
|
||||
finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret)
|
||||
if _, err := hs.c.writeHandshakeRecord(finished, &hs.finishedHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(out, finished.verifyData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processCertsFromClient takes a chain of client certificates either from a
|
||||
// Certificates message or from a sessionState and verifies them. It returns
|
||||
// the public key of the leaf certificate.
|
||||
func (c *Conn) processCertsFromClient(certificate Certificate) error {
|
||||
certificates := certificate.Certificate
|
||||
certs := make([]*x509.Certificate, len(certificates))
|
||||
var err error
|
||||
for i, asn1Data := range certificates {
|
||||
if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("tls: failed to parse client certificate: " + err.Error())
|
||||
}
|
||||
if certs[i].PublicKeyAlgorithm == x509.RSA && certs[i].PublicKey.(*rsa.PublicKey).N.BitLen() > maxRSAKeySize {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return fmt.Errorf("tls: client sent certificate containing RSA key larger than %d bits", maxRSAKeySize)
|
||||
}
|
||||
}
|
||||
|
||||
if len(certs) == 0 && requiresClientCert(c.config.ClientAuth) {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("tls: client didn't provide a certificate")
|
||||
}
|
||||
|
||||
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: c.config.ClientCAs,
|
||||
CurrentTime: c.config.time(),
|
||||
Intermediates: x509.NewCertPool(),
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
chains, err := certs[0].Verify(opts)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
|
||||
}
|
||||
|
||||
c.verifiedChains = chains
|
||||
}
|
||||
|
||||
c.peerCertificates = certs
|
||||
c.ocspResponse = certificate.OCSPStaple
|
||||
c.scts = certificate.SignedCertificateTimestamps
|
||||
|
||||
if len(certs) > 0 {
|
||||
switch certs[0].PublicKey.(type) {
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
|
||||
default:
|
||||
c.sendAlert(alertUnsupportedCertificate)
|
||||
return fmt.Errorf("tls: client certificate contains an unsupported public key of type %T", certs[0].PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
if c.config.VerifyPeerCertificate != nil {
|
||||
if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newClientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {
|
||||
supportedVersions := clientHello.supportedVersions
|
||||
if len(clientHello.supportedVersions) == 0 {
|
||||
supportedVersions = supportedVersionsFromMax(clientHello.vers)
|
||||
}
|
||||
|
||||
return toClientHelloInfo(&clientHelloInfo{
|
||||
CipherSuites: clientHello.cipherSuites,
|
||||
ServerName: clientHello.serverName,
|
||||
SupportedCurves: clientHello.supportedCurves,
|
||||
SupportedPoints: clientHello.supportedPoints,
|
||||
SignatureSchemes: clientHello.supportedSignatureAlgorithms,
|
||||
SupportedProtos: clientHello.alpnProtocols,
|
||||
SupportedVersions: supportedVersions,
|
||||
Conn: c.conn,
|
||||
config: toConfig(c.config),
|
||||
ctx: ctx,
|
||||
})
|
||||
}
|
|
@ -1,979 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// maxClientPSKIdentities is the number of client PSK identities the server will
|
||||
// attempt to validate. It will ignore the rest not to let cheap ClientHello
|
||||
// messages cause too much work in session ticket decryption attempts.
|
||||
const maxClientPSKIdentities = 5
|
||||
|
||||
type serverHandshakeStateTLS13 struct {
|
||||
c *Conn
|
||||
ctx context.Context
|
||||
clientHello *clientHelloMsg
|
||||
hello *serverHelloMsg
|
||||
alpnNegotiationErr error
|
||||
encryptedExtensions *encryptedExtensionsMsg
|
||||
sentDummyCCS bool
|
||||
usingPSK bool
|
||||
suite *cipherSuiteTLS13
|
||||
cert *Certificate
|
||||
sigAlg SignatureScheme
|
||||
earlySecret []byte
|
||||
sharedKey []byte
|
||||
handshakeSecret []byte
|
||||
masterSecret []byte
|
||||
trafficSecret []byte // client_application_traffic_secret_0
|
||||
transcript hash.Hash
|
||||
clientFinished []byte
|
||||
earlyData bool
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
if needFIPS() {
|
||||
return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
|
||||
}
|
||||
|
||||
// For an overview of the TLS 1.3 handshake, see RFC 8446, Section 2.
|
||||
if err := hs.processClientHello(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.checkForResumption(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.pickCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.buffering = true
|
||||
if err := hs.sendServerParameters(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendServerCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.sendServerFinished(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Note that at this point we could start sending application data without
|
||||
// waiting for the client's second flight, but the application might not
|
||||
// expect the lack of replay protection of the ClientHello parameters.
|
||||
if _, err := c.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readClientCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readClientFinished(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isHandshakeComplete.Store(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
||||
c := hs.c
|
||||
|
||||
hs.hello = new(serverHelloMsg)
|
||||
hs.encryptedExtensions = new(encryptedExtensionsMsg)
|
||||
|
||||
// TLS 1.3 froze the ServerHello.legacy_version field, and uses
|
||||
// supported_versions instead. See RFC 8446, sections 4.1.3 and 4.2.1.
|
||||
hs.hello.vers = VersionTLS12
|
||||
hs.hello.supportedVersion = c.vers
|
||||
|
||||
if len(hs.clientHello.supportedVersions) == 0 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client used the legacy version field to negotiate TLS 1.3")
|
||||
}
|
||||
|
||||
// Abort if the client is doing a fallback and landing lower than what we
|
||||
// support. See RFC 7507, which however does not specify the interaction
|
||||
// with supported_versions. The only difference is that with
|
||||
// supported_versions a client has a chance to attempt a [TLS 1.2, TLS 1.4]
|
||||
// handshake in case TLS 1.3 is broken but 1.2 is not. Alas, in that case,
|
||||
// it will have to drop the TLS_FALLBACK_SCSV protection if it falls back to
|
||||
// TLS 1.2, because a TLS 1.3 server would abort here. The situation before
|
||||
// supported_versions was not better because there was just no way to do a
|
||||
// TLS 1.4 handshake without risking the server selecting TLS 1.3.
|
||||
for _, id := range hs.clientHello.cipherSuites {
|
||||
if id == TLS_FALLBACK_SCSV {
|
||||
// Use c.vers instead of max(supported_versions) because an attacker
|
||||
// could defeat this by adding an arbitrary high version otherwise.
|
||||
if c.vers < c.config.maxSupportedVersion(roleServer) {
|
||||
c.sendAlert(alertInappropriateFallback)
|
||||
return errors.New("tls: client using inappropriate protocol fallback")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(hs.clientHello.compressionMethods) != 1 ||
|
||||
hs.clientHello.compressionMethods[0] != compressionNone {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: TLS 1.3 client supports illegal compression methods")
|
||||
}
|
||||
|
||||
hs.hello.random = make([]byte, 32)
|
||||
if _, err := io.ReadFull(c.config.rand(), hs.hello.random); err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hs.clientHello.secureRenegotiation) != 0 {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: initial handshake had non-empty renegotiation extension")
|
||||
}
|
||||
|
||||
hs.hello.sessionId = hs.clientHello.sessionId
|
||||
hs.hello.compressionMethod = compressionNone
|
||||
|
||||
preferenceList := defaultCipherSuitesTLS13
|
||||
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
|
||||
preferenceList = defaultCipherSuitesTLS13NoAES
|
||||
}
|
||||
for _, suiteID := range preferenceList {
|
||||
hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID)
|
||||
if hs.suite != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if hs.suite == nil {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no cipher suite supported by both client and server")
|
||||
}
|
||||
c.cipherSuite = hs.suite.id
|
||||
hs.hello.cipherSuite = hs.suite.id
|
||||
hs.transcript = hs.suite.hash.New()
|
||||
|
||||
// Pick the ECDHE group in server preference order, but give priority to
|
||||
// groups with a key share, to avoid a HelloRetryRequest round-trip.
|
||||
var selectedGroup CurveID
|
||||
var clientKeyShare *keyShare
|
||||
GroupSelection:
|
||||
for _, preferredGroup := range c.config.curvePreferences() {
|
||||
for _, ks := range hs.clientHello.keyShares {
|
||||
if ks.group == preferredGroup {
|
||||
selectedGroup = ks.group
|
||||
clientKeyShare = &ks
|
||||
break GroupSelection
|
||||
}
|
||||
}
|
||||
if selectedGroup != 0 {
|
||||
continue
|
||||
}
|
||||
for _, group := range hs.clientHello.supportedCurves {
|
||||
if group == preferredGroup {
|
||||
selectedGroup = group
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if selectedGroup == 0 {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: no ECDHE curve supported by both client and server")
|
||||
}
|
||||
if clientKeyShare == nil {
|
||||
if err := hs.doHelloRetryRequest(selectedGroup); err != nil {
|
||||
return err
|
||||
}
|
||||
clientKeyShare = &hs.clientHello.keyShares[0]
|
||||
}
|
||||
|
||||
if _, ok := curveForCurveID(selectedGroup); !ok {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
key, err := generateECDHEKey(c.config.rand(), selectedGroup)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
|
||||
peerKey, err := key.Curve().NewPublicKey(clientKeyShare.data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid client key share")
|
||||
}
|
||||
hs.sharedKey, err = key.ECDH(peerKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid client key share")
|
||||
}
|
||||
|
||||
if c.quic != nil {
|
||||
if hs.clientHello.quicTransportParameters == nil {
|
||||
// RFC 9001 Section 8.2.
|
||||
c.sendAlert(alertMissingExtension)
|
||||
return errors.New("tls: client did not send a quic_transport_parameters extension")
|
||||
}
|
||||
c.quicSetTransportParameters(hs.clientHello.quicTransportParameters)
|
||||
} else {
|
||||
if hs.clientHello.quicTransportParameters != nil {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: client sent an unexpected quic_transport_parameters extension")
|
||||
}
|
||||
}
|
||||
|
||||
c.serverName = hs.clientHello.serverName
|
||||
|
||||
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
||||
if err != nil {
|
||||
hs.alpnNegotiationErr = err
|
||||
}
|
||||
hs.encryptedExtensions.alpnProtocol = selectedProto
|
||||
c.clientProtocol = selectedProto
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
||||
c := hs.c
|
||||
|
||||
if c.config.SessionTicketsDisabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
modeOK := false
|
||||
for _, mode := range hs.clientHello.pskModes {
|
||||
if mode == pskModeDHE {
|
||||
modeOK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !modeOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(hs.clientHello.pskIdentities) != len(hs.clientHello.pskBinders) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: invalid or missing PSK binders")
|
||||
}
|
||||
if len(hs.clientHello.pskIdentities) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, identity := range hs.clientHello.pskIdentities {
|
||||
if i >= maxClientPSKIdentities {
|
||||
break
|
||||
}
|
||||
|
||||
plaintext, _ := c.decryptTicket(identity.label)
|
||||
if plaintext == nil {
|
||||
continue
|
||||
}
|
||||
sessionState := new(sessionStateTLS13)
|
||||
if ok := sessionState.unmarshal(plaintext); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if hs.clientHello.earlyData {
|
||||
if sessionState.maxEarlyData == 0 {
|
||||
c.sendAlert(alertUnsupportedExtension)
|
||||
return errors.New("tls: client sent unexpected early data")
|
||||
}
|
||||
|
||||
if hs.alpnNegotiationErr == nil && sessionState.alpn == c.clientProtocol &&
|
||||
c.extraConfig != nil && c.extraConfig.Enable0RTT &&
|
||||
c.extraConfig.Accept0RTT != nil && c.extraConfig.Accept0RTT(sessionState.appData) {
|
||||
hs.encryptedExtensions.earlyData = true
|
||||
}
|
||||
}
|
||||
|
||||
createdAt := time.Unix(int64(sessionState.createdAt), 0)
|
||||
if c.config.time().Sub(createdAt) > maxSessionTicketLifetime {
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't check the obfuscated ticket age because it's affected by
|
||||
// clock skew and it's only a freshness signal useful for shrinking the
|
||||
// window for replay attacks, which don't affect us as we don't do 0-RTT.
|
||||
|
||||
pskSuite := cipherSuiteTLS13ByID(sessionState.cipherSuite)
|
||||
if pskSuite == nil || pskSuite.hash != hs.suite.hash {
|
||||
continue
|
||||
}
|
||||
|
||||
// PSK connections don't re-establish client certificates, but carry
|
||||
// them over in the session ticket. Ensure the presence of client certs
|
||||
// in the ticket is consistent with the configured requirements.
|
||||
sessionHasClientCerts := len(sessionState.certificate.Certificate) != 0
|
||||
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
||||
if needClientCerts && !sessionHasClientCerts {
|
||||
continue
|
||||
}
|
||||
if sessionHasClientCerts && c.config.ClientAuth == NoClientCert {
|
||||
continue
|
||||
}
|
||||
|
||||
psk := hs.suite.expandLabel(sessionState.resumptionSecret, "resumption",
|
||||
nil, hs.suite.hash.Size())
|
||||
hs.earlySecret = hs.suite.extract(psk, nil)
|
||||
binderKey := hs.suite.deriveSecret(hs.earlySecret, resumptionBinderLabel, nil)
|
||||
// Clone the transcript in case a HelloRetryRequest was recorded.
|
||||
transcript := cloneHash(hs.transcript, hs.suite.hash)
|
||||
if transcript == nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: internal error: failed to clone hash")
|
||||
}
|
||||
clientHelloBytes, err := hs.clientHello.marshalWithoutBinders()
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
transcript.Write(clientHelloBytes)
|
||||
pskBinder := hs.suite.finishedHash(binderKey, transcript)
|
||||
if !hmac.Equal(hs.clientHello.pskBinders[i], pskBinder) {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid PSK binder")
|
||||
}
|
||||
|
||||
if c.quic != nil && hs.clientHello.earlyData && hs.encryptedExtensions.earlyData && i == 0 &&
|
||||
sessionState.maxEarlyData > 0 && sessionState.cipherSuite == hs.suite.id {
|
||||
hs.earlyData = true
|
||||
|
||||
transcript := hs.suite.hash.New()
|
||||
if err := transcriptMsg(hs.clientHello, transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript)
|
||||
c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret)
|
||||
}
|
||||
|
||||
c.didResume = true
|
||||
if err := c.processCertsFromClient(sessionState.certificate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.hello.selectedIdentityPresent = true
|
||||
hs.hello.selectedIdentity = uint16(i)
|
||||
hs.usingPSK = true
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cloneHash uses the encoding.BinaryMarshaler and encoding.BinaryUnmarshaler
|
||||
// interfaces implemented by standard library hashes to clone the state of in
|
||||
// to a new instance of h. It returns nil if the operation fails.
|
||||
func cloneHash(in hash.Hash, h crypto.Hash) hash.Hash {
|
||||
// Recreate the interface to avoid importing encoding.
|
||||
type binaryMarshaler interface {
|
||||
MarshalBinary() (data []byte, err error)
|
||||
UnmarshalBinary(data []byte) error
|
||||
}
|
||||
marshaler, ok := in.(binaryMarshaler)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
state, err := marshaler.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out := h.New()
|
||||
unmarshaler, ok := out.(binaryMarshaler)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := unmarshaler.UnmarshalBinary(state); err != nil {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) pickCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
// Only one of PSK and certificates are used at a time.
|
||||
if hs.usingPSK {
|
||||
return nil
|
||||
}
|
||||
|
||||
// signature_algorithms is required in TLS 1.3. See RFC 8446, Section 4.2.3.
|
||||
if len(hs.clientHello.supportedSignatureAlgorithms) == 0 {
|
||||
return c.sendAlert(alertMissingExtension)
|
||||
}
|
||||
|
||||
certificate, err := c.config.getCertificate(newClientHelloInfo(hs.ctx, c, hs.clientHello))
|
||||
if err != nil {
|
||||
if err == errNoCertificates {
|
||||
c.sendAlert(alertUnrecognizedName)
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
}
|
||||
return err
|
||||
}
|
||||
hs.sigAlg, err = selectSignatureScheme(c.vers, certificate, hs.clientHello.supportedSignatureAlgorithms)
|
||||
if err != nil {
|
||||
// getCertificate returned a certificate that is unsupported or
|
||||
// incompatible with the client's signature algorithms.
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return err
|
||||
}
|
||||
hs.cert = certificate
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDummyChangeCipherSpec sends a ChangeCipherSpec record for compatibility
|
||||
// with middleboxes that didn't implement TLS correctly. See RFC 8446, Appendix D.4.
|
||||
func (hs *serverHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
|
||||
if hs.c.quic != nil {
|
||||
return nil
|
||||
}
|
||||
if hs.sentDummyCCS {
|
||||
return nil
|
||||
}
|
||||
hs.sentDummyCCS = true
|
||||
|
||||
return hs.c.writeChangeCipherRecord()
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) error {
|
||||
c := hs.c
|
||||
|
||||
// The first ClientHello gets double-hashed into the transcript upon a
|
||||
// HelloRetryRequest. See RFC 8446, Section 4.4.1.
|
||||
if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
chHash := hs.transcript.Sum(nil)
|
||||
hs.transcript.Reset()
|
||||
hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
hs.transcript.Write(chHash)
|
||||
|
||||
helloRetryRequest := &serverHelloMsg{
|
||||
vers: hs.hello.vers,
|
||||
random: helloRetryRequestRandom,
|
||||
sessionId: hs.hello.sessionId,
|
||||
cipherSuite: hs.hello.cipherSuite,
|
||||
compressionMethod: hs.hello.compressionMethod,
|
||||
supportedVersion: hs.hello.supportedVersion,
|
||||
selectedGroup: selectedGroup,
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := hs.sendDummyChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// clientHelloMsg is not included in the transcript.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientHello, ok := msg.(*clientHelloMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(clientHello, msg)
|
||||
}
|
||||
|
||||
if len(clientHello.keyShares) != 1 || clientHello.keyShares[0].group != selectedGroup {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client sent invalid key share in second ClientHello")
|
||||
}
|
||||
|
||||
if clientHello.earlyData {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client indicated early data in second ClientHello")
|
||||
}
|
||||
|
||||
if illegalClientHelloChange(clientHello, hs.clientHello) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client illegally modified second ClientHello")
|
||||
}
|
||||
|
||||
if illegalClientHelloChange(clientHello, hs.clientHello) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client illegally modified second ClientHello")
|
||||
}
|
||||
|
||||
hs.clientHello = clientHello
|
||||
return nil
|
||||
}
|
||||
|
||||
// illegalClientHelloChange reports whether the two ClientHello messages are
|
||||
// different, with the exception of the changes allowed before and after a
|
||||
// HelloRetryRequest. See RFC 8446, Section 4.1.2.
|
||||
func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool {
|
||||
if len(ch.supportedVersions) != len(ch1.supportedVersions) ||
|
||||
len(ch.cipherSuites) != len(ch1.cipherSuites) ||
|
||||
len(ch.supportedCurves) != len(ch1.supportedCurves) ||
|
||||
len(ch.supportedSignatureAlgorithms) != len(ch1.supportedSignatureAlgorithms) ||
|
||||
len(ch.supportedSignatureAlgorithmsCert) != len(ch1.supportedSignatureAlgorithmsCert) ||
|
||||
len(ch.alpnProtocols) != len(ch1.alpnProtocols) {
|
||||
return true
|
||||
}
|
||||
for i := range ch.supportedVersions {
|
||||
if ch.supportedVersions[i] != ch1.supportedVersions[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ch.cipherSuites {
|
||||
if ch.cipherSuites[i] != ch1.cipherSuites[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ch.supportedCurves {
|
||||
if ch.supportedCurves[i] != ch1.supportedCurves[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ch.supportedSignatureAlgorithms {
|
||||
if ch.supportedSignatureAlgorithms[i] != ch1.supportedSignatureAlgorithms[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ch.supportedSignatureAlgorithmsCert {
|
||||
if ch.supportedSignatureAlgorithmsCert[i] != ch1.supportedSignatureAlgorithmsCert[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ch.alpnProtocols {
|
||||
if ch.alpnProtocols[i] != ch1.alpnProtocols[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return ch.vers != ch1.vers ||
|
||||
!bytes.Equal(ch.random, ch1.random) ||
|
||||
!bytes.Equal(ch.sessionId, ch1.sessionId) ||
|
||||
!bytes.Equal(ch.compressionMethods, ch1.compressionMethods) ||
|
||||
ch.serverName != ch1.serverName ||
|
||||
ch.ocspStapling != ch1.ocspStapling ||
|
||||
!bytes.Equal(ch.supportedPoints, ch1.supportedPoints) ||
|
||||
ch.ticketSupported != ch1.ticketSupported ||
|
||||
!bytes.Equal(ch.sessionTicket, ch1.sessionTicket) ||
|
||||
ch.secureRenegotiationSupported != ch1.secureRenegotiationSupported ||
|
||||
!bytes.Equal(ch.secureRenegotiation, ch1.secureRenegotiation) ||
|
||||
ch.scts != ch1.scts ||
|
||||
!bytes.Equal(ch.cookie, ch1.cookie) ||
|
||||
!bytes.Equal(ch.pskModes, ch1.pskModes)
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
||||
c := hs.c
|
||||
|
||||
if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := hs.sendDummyChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
earlySecret := hs.earlySecret
|
||||
if earlySecret == nil {
|
||||
earlySecret = hs.suite.extract(nil, nil)
|
||||
}
|
||||
hs.handshakeSecret = hs.suite.extract(hs.sharedKey,
|
||||
hs.suite.deriveSecret(earlySecret, "derived", nil))
|
||||
|
||||
clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,
|
||||
clientHandshakeTrafficLabel, hs.transcript)
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
|
||||
serverSecret := hs.suite.deriveSecret(hs.handshakeSecret,
|
||||
serverHandshakeTrafficLabel, hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
if c.hand.Len() != 0 {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelHandshake, hs.suite.id, serverSecret)
|
||||
c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, clientSecret)
|
||||
}
|
||||
|
||||
err := c.config.writeKeyLog(keyLogLabelClientHandshake, hs.clientHello.random, clientSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
err = c.config.writeKeyLog(keyLogLabelServerHandshake, hs.clientHello.random, serverSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
||||
if err != nil {
|
||||
c.sendAlert(alertNoApplicationProtocol)
|
||||
return err
|
||||
}
|
||||
hs.encryptedExtensions.alpnProtocol = selectedProto
|
||||
c.clientProtocol = selectedProto
|
||||
|
||||
if c.quic != nil {
|
||||
p, err := c.quicGetTransportParameters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hs.encryptedExtensions.quicTransportParameters = p
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(hs.encryptedExtensions, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) requestClientCert() bool {
|
||||
return hs.c.config.ClientAuth >= RequestClientCert && !hs.usingPSK
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
// Only one of PSK and certificates are used at a time.
|
||||
if hs.usingPSK {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hs.requestClientCert() {
|
||||
// Request a client certificate
|
||||
certReq := new(certificateRequestMsgTLS13)
|
||||
certReq.ocspStapling = true
|
||||
certReq.scts = true
|
||||
certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
|
||||
if c.config.ClientCAs != nil {
|
||||
certReq.certificateAuthorities = c.config.ClientCAs.Subjects()
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(certReq, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
certMsg := new(certificateMsgTLS13)
|
||||
|
||||
certMsg.certificate = *hs.cert
|
||||
certMsg.scts = hs.clientHello.scts && len(hs.cert.SignedCertificateTimestamps) > 0
|
||||
certMsg.ocspStapling = hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(certMsg, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certVerifyMsg := new(certificateVerifyMsg)
|
||||
certVerifyMsg.hasSignatureAlgorithm = true
|
||||
certVerifyMsg.signatureAlgorithm = hs.sigAlg
|
||||
|
||||
sigType, sigHash, err := typeAndHashFromSignatureScheme(hs.sigAlg)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
signed := signedMessage(sigHash, serverSignatureContext, hs.transcript)
|
||||
signOpts := crypto.SignerOpts(sigHash)
|
||||
if sigType == signatureRSAPSS {
|
||||
signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
|
||||
}
|
||||
sig, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)
|
||||
if err != nil {
|
||||
public := hs.cert.PrivateKey.(crypto.Signer).Public()
|
||||
if rsaKey, ok := public.(*rsa.PublicKey); ok && sigType == signatureRSAPSS &&
|
||||
rsaKey.N.BitLen()/8 < sigHash.Size()*2+2 { // key too small for RSA-PSS
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
} else {
|
||||
c.sendAlert(alertInternalError)
|
||||
}
|
||||
return errors.New("tls: failed to sign handshake: " + err.Error())
|
||||
}
|
||||
certVerifyMsg.signature = sig
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(certVerifyMsg, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
|
||||
c := hs.c
|
||||
|
||||
finished := &finishedMsg{
|
||||
verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
|
||||
}
|
||||
|
||||
if _, err := hs.c.writeHandshakeRecord(finished, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Derive secrets that take context through the server Finished.
|
||||
|
||||
hs.masterSecret = hs.suite.extract(nil,
|
||||
hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil))
|
||||
|
||||
hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
clientApplicationTrafficLabel, hs.transcript)
|
||||
serverSecret := hs.suite.deriveSecret(hs.masterSecret,
|
||||
serverApplicationTrafficLabel, hs.transcript)
|
||||
c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
|
||||
|
||||
if c.quic != nil {
|
||||
if c.hand.Len() != 0 {
|
||||
// TODO: Handle this in setTrafficSecret?
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelApplication, hs.suite.id, serverSecret)
|
||||
}
|
||||
|
||||
err := c.config.writeKeyLog(keyLogLabelClientTraffic, hs.clientHello.random, hs.trafficSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
err = c.config.writeKeyLog(keyLogLabelServerTraffic, hs.clientHello.random, serverSecret)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
|
||||
c.ekm = hs.suite.exportKeyingMaterial(hs.masterSecret, hs.transcript)
|
||||
|
||||
// If we did not request client certificates, at this point we can
|
||||
// precompute the client finished and roll the transcript forward to send
|
||||
// session tickets in our first flight.
|
||||
if !hs.requestClientCert() {
|
||||
if err := hs.sendSessionTickets(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool {
|
||||
if hs.c.config.SessionTicketsDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
// QUIC tickets are sent by QUICConn.SendSessionTicket, not automatically.
|
||||
if hs.c.quic != nil {
|
||||
return false
|
||||
}
|
||||
// Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9.
|
||||
for _, pskMode := range hs.clientHello.pskModes {
|
||||
if pskMode == pskModeDHE {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
||||
c := hs.c
|
||||
|
||||
hs.clientFinished = hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
|
||||
finishedMsg := &finishedMsg{
|
||||
verifyData: hs.clientFinished,
|
||||
}
|
||||
if err := transcriptMsg(finishedMsg, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
resumptionLabel, hs.transcript)
|
||||
|
||||
if !hs.shouldSendSessionTickets() {
|
||||
return nil
|
||||
}
|
||||
return c.sendSessionTicket(false)
|
||||
}
|
||||
|
||||
func (c *Conn) sendSessionTicket(earlyData bool) error {
|
||||
suite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||
if suite == nil {
|
||||
return errors.New("tls: internal error: unknown cipher suite")
|
||||
}
|
||||
|
||||
m := new(newSessionTicketMsgTLS13)
|
||||
|
||||
var certsFromClient [][]byte
|
||||
for _, cert := range c.peerCertificates {
|
||||
certsFromClient = append(certsFromClient, cert.Raw)
|
||||
}
|
||||
state := sessionStateTLS13{
|
||||
cipherSuite: suite.id,
|
||||
createdAt: uint64(c.config.time().Unix()),
|
||||
resumptionSecret: c.resumptionSecret,
|
||||
certificate: Certificate{
|
||||
Certificate: certsFromClient,
|
||||
OCSPStaple: c.ocspResponse,
|
||||
SignedCertificateTimestamps: c.scts,
|
||||
},
|
||||
alpn: c.clientProtocol,
|
||||
}
|
||||
if earlyData {
|
||||
state.maxEarlyData = 0xffffffff
|
||||
state.appData = c.extraConfig.GetAppDataForSessionTicket()
|
||||
}
|
||||
stateBytes, err := state.marshal()
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return err
|
||||
}
|
||||
m.label, err = c.encryptTicket(stateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.lifetime = uint32(maxSessionTicketLifetime / time.Second)
|
||||
|
||||
// ticket_age_add is a random 32-bit value. See RFC 8446, section 4.6.1
|
||||
// The value is not stored anywhere; we never need to check the ticket age
|
||||
// because 0-RTT is not supported.
|
||||
ageAdd := make([]byte, 4)
|
||||
_, err = c.config.rand().Read(ageAdd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if earlyData {
|
||||
// RFC 9001, Section 4.6.1
|
||||
m.maxEarlyData = 0xffffffff
|
||||
}
|
||||
|
||||
if _, err := c.writeHandshakeRecord(m, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) readClientCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
if !hs.requestClientCert() {
|
||||
// Make sure the connection is still being verified whether or not
|
||||
// the server requested a client certificate.
|
||||
if c.config.VerifyConnection != nil {
|
||||
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we requested a client certificate, then the client must send a
|
||||
// certificate message. If it's empty, no CertificateVerify is sent.
|
||||
|
||||
msg, err := c.readHandshake(hs.transcript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certMsg, ok := msg.(*certificateMsgTLS13)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certMsg, msg)
|
||||
}
|
||||
|
||||
if err := c.processCertsFromClient(certMsg.certificate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.config.VerifyConnection != nil {
|
||||
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(certMsg.certificate.Certificate) != 0 {
|
||||
// certificateVerifyMsg is included in the transcript, but not until
|
||||
// after we verify the handshake signature, since the state before
|
||||
// this message was sent is used.
|
||||
msg, err = c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certVerify, ok := msg.(*certificateVerifyMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certVerify, msg)
|
||||
}
|
||||
|
||||
// See RFC 8446, Section 4.4.3.
|
||||
if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client certificate used with invalid signature algorithm")
|
||||
}
|
||||
sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: client certificate used with invalid signature algorithm")
|
||||
}
|
||||
signed := signedMessage(sigHash, clientSignatureContext, hs.transcript)
|
||||
if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,
|
||||
sigHash, signed, certVerify.signature); err != nil {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid signature by the client certificate: " + err.Error())
|
||||
}
|
||||
|
||||
if err := transcriptMsg(certVerify, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we waited until the client certificates to send session tickets, we
|
||||
// are ready to do it now.
|
||||
if err := hs.sendSessionTickets(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *serverHandshakeStateTLS13) readClientFinished() error {
|
||||
c := hs.c
|
||||
|
||||
// finishedMsg is not included in the transcript.
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finished, ok := msg.(*finishedMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(finished, msg)
|
||||
}
|
||||
|
||||
if !hmac.Equal(hs.clientFinished, finished.verifyData) {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid client finished hash")
|
||||
}
|
||||
|
||||
c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,366 +0,0 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// a keyAgreement implements the client and server side of a TLS key agreement
|
||||
// protocol by generating and processing key exchange messages.
|
||||
type keyAgreement interface {
|
||||
// On the server side, the first two methods are called in order.
|
||||
|
||||
// In the case that the key agreement protocol doesn't use a
|
||||
// ServerKeyExchange message, generateServerKeyExchange can return nil,
|
||||
// nil.
|
||||
generateServerKeyExchange(*config, *Certificate, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error)
|
||||
processClientKeyExchange(*config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error)
|
||||
|
||||
// On the client side, the next two methods are called in order.
|
||||
|
||||
// This method may not be called if the server doesn't send a
|
||||
// ServerKeyExchange message.
|
||||
processServerKeyExchange(*config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error
|
||||
generateClientKeyExchange(*config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
|
||||
}
|
||||
|
||||
var errClientKeyExchange = errors.New("tls: invalid ClientKeyExchange message")
|
||||
var errServerKeyExchange = errors.New("tls: invalid ServerKeyExchange message")
|
||||
|
||||
// rsaKeyAgreement implements the standard TLS key agreement where the client
|
||||
// encrypts the pre-master secret to the server's public key.
|
||||
type rsaKeyAgreement struct{}
|
||||
|
||||
func (ka rsaKeyAgreement) generateServerKeyExchange(config *config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) processClientKeyExchange(config *config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||
if len(ckx.ciphertext) < 2 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
ciphertextLen := int(ckx.ciphertext[0])<<8 | int(ckx.ciphertext[1])
|
||||
if ciphertextLen != len(ckx.ciphertext)-2 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
ciphertext := ckx.ciphertext[2:]
|
||||
|
||||
priv, ok := cert.PrivateKey.(crypto.Decrypter)
|
||||
if !ok {
|
||||
return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
|
||||
}
|
||||
// Perform constant time RSA PKCS #1 v1.5 decryption
|
||||
preMasterSecret, err := priv.Decrypt(config.rand(), ciphertext, &rsa.PKCS1v15DecryptOptions{SessionKeyLen: 48})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We don't check the version number in the premaster secret. For one,
|
||||
// by checking it, we would leak information about the validity of the
|
||||
// encrypted pre-master secret. Secondly, it provides only a small
|
||||
// benefit against a downgrade attack and some implementations send the
|
||||
// wrong version anyway. See the discussion at the end of section
|
||||
// 7.4.7.1 of RFC 4346.
|
||||
return preMasterSecret, nil
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) processServerKeyExchange(config *config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
||||
return errors.New("tls: unexpected ServerKeyExchange")
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) generateClientKeyExchange(config *config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
||||
preMasterSecret := make([]byte, 48)
|
||||
preMasterSecret[0] = byte(clientHello.vers >> 8)
|
||||
preMasterSecret[1] = byte(clientHello.vers)
|
||||
_, err := io.ReadFull(config.rand(), preMasterSecret[2:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("tls: server certificate contains incorrect key type for selected ciphersuite")
|
||||
}
|
||||
encrypted, err := rsa.EncryptPKCS1v15(config.rand(), rsaKey, preMasterSecret)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ckx := new(clientKeyExchangeMsg)
|
||||
ckx.ciphertext = make([]byte, len(encrypted)+2)
|
||||
ckx.ciphertext[0] = byte(len(encrypted) >> 8)
|
||||
ckx.ciphertext[1] = byte(len(encrypted))
|
||||
copy(ckx.ciphertext[2:], encrypted)
|
||||
return preMasterSecret, ckx, nil
|
||||
}
|
||||
|
||||
// sha1Hash calculates a SHA1 hash over the given byte slices.
|
||||
func sha1Hash(slices [][]byte) []byte {
|
||||
hsha1 := sha1.New()
|
||||
for _, slice := range slices {
|
||||
hsha1.Write(slice)
|
||||
}
|
||||
return hsha1.Sum(nil)
|
||||
}
|
||||
|
||||
// md5SHA1Hash implements TLS 1.0's hybrid hash function which consists of the
|
||||
// concatenation of an MD5 and SHA1 hash.
|
||||
func md5SHA1Hash(slices [][]byte) []byte {
|
||||
md5sha1 := make([]byte, md5.Size+sha1.Size)
|
||||
hmd5 := md5.New()
|
||||
for _, slice := range slices {
|
||||
hmd5.Write(slice)
|
||||
}
|
||||
copy(md5sha1, hmd5.Sum(nil))
|
||||
copy(md5sha1[md5.Size:], sha1Hash(slices))
|
||||
return md5sha1
|
||||
}
|
||||
|
||||
// hashForServerKeyExchange hashes the given slices and returns their digest
|
||||
// using the given hash function (for >= TLS 1.2) or using a default based on
|
||||
// the sigType (for earlier TLS versions). For Ed25519 signatures, which don't
|
||||
// do pre-hashing, it returns the concatenation of the slices.
|
||||
func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte {
|
||||
if sigType == signatureEd25519 {
|
||||
var signed []byte
|
||||
for _, slice := range slices {
|
||||
signed = append(signed, slice...)
|
||||
}
|
||||
return signed
|
||||
}
|
||||
if version >= VersionTLS12 {
|
||||
h := hashFunc.New()
|
||||
for _, slice := range slices {
|
||||
h.Write(slice)
|
||||
}
|
||||
digest := h.Sum(nil)
|
||||
return digest
|
||||
}
|
||||
if sigType == signatureECDSA {
|
||||
return sha1Hash(slices)
|
||||
}
|
||||
return md5SHA1Hash(slices)
|
||||
}
|
||||
|
||||
// ecdheKeyAgreement implements a TLS key agreement where the server
|
||||
// generates an ephemeral EC public/private key pair and signs it. The
|
||||
// pre-master secret is then calculated using ECDH. The signature may
|
||||
// be ECDSA, Ed25519 or RSA.
|
||||
type ecdheKeyAgreement struct {
|
||||
version uint16
|
||||
isRSA bool
|
||||
key *ecdh.PrivateKey
|
||||
|
||||
// ckx and preMasterSecret are generated in processServerKeyExchange
|
||||
// and returned in generateClientKeyExchange.
|
||||
ckx *clientKeyExchangeMsg
|
||||
preMasterSecret []byte
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||
var curveID CurveID
|
||||
for _, c := range clientHello.supportedCurves {
|
||||
if config.supportsCurve(c) {
|
||||
curveID = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if curveID == 0 {
|
||||
return nil, errors.New("tls: no supported elliptic curves offered")
|
||||
}
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
|
||||
key, err := generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ka.key = key
|
||||
|
||||
// See RFC 4492, Section 5.4.
|
||||
ecdhePublic := key.PublicKey().Bytes()
|
||||
serverECDHEParams := make([]byte, 1+2+1+len(ecdhePublic))
|
||||
serverECDHEParams[0] = 3 // named curve
|
||||
serverECDHEParams[1] = byte(curveID >> 8)
|
||||
serverECDHEParams[2] = byte(curveID)
|
||||
serverECDHEParams[3] = byte(len(ecdhePublic))
|
||||
copy(serverECDHEParams[4:], ecdhePublic)
|
||||
|
||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tls: certificate private key of type %T does not implement crypto.Signer", cert.PrivateKey)
|
||||
}
|
||||
|
||||
var signatureAlgorithm SignatureScheme
|
||||
var sigType uint8
|
||||
var sigHash crypto.Hash
|
||||
if ka.version >= VersionTLS12 {
|
||||
signatureAlgorithm, err = selectSignatureScheme(ka.version, cert, clientHello.supportedSignatureAlgorithms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sigType, sigHash, err = legacyTypeAndHashFromPublicKey(priv.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
|
||||
return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
|
||||
}
|
||||
|
||||
signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, hello.random, serverECDHEParams)
|
||||
|
||||
signOpts := crypto.SignerOpts(sigHash)
|
||||
if sigType == signatureRSAPSS {
|
||||
signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
|
||||
}
|
||||
sig, err := priv.Sign(config.rand(), signed, signOpts)
|
||||
if err != nil {
|
||||
return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
|
||||
}
|
||||
|
||||
skx := new(serverKeyExchangeMsg)
|
||||
sigAndHashLen := 0
|
||||
if ka.version >= VersionTLS12 {
|
||||
sigAndHashLen = 2
|
||||
}
|
||||
skx.key = make([]byte, len(serverECDHEParams)+sigAndHashLen+2+len(sig))
|
||||
copy(skx.key, serverECDHEParams)
|
||||
k := skx.key[len(serverECDHEParams):]
|
||||
if ka.version >= VersionTLS12 {
|
||||
k[0] = byte(signatureAlgorithm >> 8)
|
||||
k[1] = byte(signatureAlgorithm)
|
||||
k = k[2:]
|
||||
}
|
||||
k[0] = byte(len(sig) >> 8)
|
||||
k[1] = byte(len(sig))
|
||||
copy(k[2:], sig)
|
||||
|
||||
return skx, nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) processClientKeyExchange(config *config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
|
||||
peerKey, err := ka.key.Curve().NewPublicKey(ckx.ciphertext[1:])
|
||||
if err != nil {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
preMasterSecret, err := ka.key.ECDH(peerKey)
|
||||
if err != nil {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
|
||||
return preMasterSecret, nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
||||
if len(skx.key) < 4 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
if skx.key[0] != 3 { // named curve
|
||||
return errors.New("tls: server selected unsupported curve")
|
||||
}
|
||||
curveID := CurveID(skx.key[1])<<8 | CurveID(skx.key[2])
|
||||
|
||||
publicLen := int(skx.key[3])
|
||||
if publicLen+4 > len(skx.key) {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
serverECDHEParams := skx.key[:4+publicLen]
|
||||
publicKey := serverECDHEParams[4:]
|
||||
|
||||
sig := skx.key[4+publicLen:]
|
||||
if len(sig) < 2 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return errors.New("tls: server selected unsupported curve")
|
||||
}
|
||||
|
||||
key, err := generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ka.key = key
|
||||
|
||||
peerKey, err := key.Curve().NewPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
ka.preMasterSecret, err = key.ECDH(peerKey)
|
||||
if err != nil {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
ourPublicKey := key.PublicKey().Bytes()
|
||||
ka.ckx = new(clientKeyExchangeMsg)
|
||||
ka.ckx.ciphertext = make([]byte, 1+len(ourPublicKey))
|
||||
ka.ckx.ciphertext[0] = byte(len(ourPublicKey))
|
||||
copy(ka.ckx.ciphertext[1:], ourPublicKey)
|
||||
|
||||
var sigType uint8
|
||||
var sigHash crypto.Hash
|
||||
if ka.version >= VersionTLS12 {
|
||||
signatureAlgorithm := SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
|
||||
sig = sig[2:]
|
||||
if len(sig) < 2 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
if !isSupportedSignatureAlgorithm(signatureAlgorithm, clientHello.supportedSignatureAlgorithms) {
|
||||
return errors.New("tls: certificate used with invalid signature algorithm")
|
||||
}
|
||||
sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
sigType, sigHash, err = legacyTypeAndHashFromPublicKey(cert.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
sigLen := int(sig[0])<<8 | int(sig[1])
|
||||
if sigLen+2 != len(sig) {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
sig = sig[2:]
|
||||
|
||||
signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, serverHello.random, serverECDHEParams)
|
||||
if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil {
|
||||
return errors.New("tls: invalid signature by the server certificate: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
||||
if ka.ckx == nil {
|
||||
return nil, nil, errors.New("tls: missing ServerKeyExchange message")
|
||||
}
|
||||
|
||||
return ka.preMasterSecret, ka.ckx, nil
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// This file contains the functions necessary to compute the TLS 1.3 key
|
||||
// schedule. See RFC 8446, Section 7.
|
||||
|
||||
const (
|
||||
resumptionBinderLabel = "res binder"
|
||||
clientEarlyTrafficLabel = "c e traffic"
|
||||
clientHandshakeTrafficLabel = "c hs traffic"
|
||||
serverHandshakeTrafficLabel = "s hs traffic"
|
||||
clientApplicationTrafficLabel = "c ap traffic"
|
||||
serverApplicationTrafficLabel = "s ap traffic"
|
||||
exporterLabel = "exp master"
|
||||
resumptionLabel = "res master"
|
||||
trafficUpdateLabel = "traffic upd"
|
||||
)
|
||||
|
||||
// expandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
|
||||
func (c *cipherSuiteTLS13) expandLabel(secret []byte, label string, context []byte, length int) []byte {
|
||||
var hkdfLabel cryptobyte.Builder
|
||||
hkdfLabel.AddUint16(uint16(length))
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes([]byte("tls13 "))
|
||||
b.AddBytes([]byte(label))
|
||||
})
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(context)
|
||||
})
|
||||
hkdfLabelBytes, err := hkdfLabel.Bytes()
|
||||
if err != nil {
|
||||
// Rather than calling BytesOrPanic, we explicitly handle this error, in
|
||||
// order to provide a reasonable error message. It should be basically
|
||||
// impossible for this to panic, and routing errors back through the
|
||||
// tree rooted in this function is quite painful. The labels are fixed
|
||||
// size, and the context is either a fixed-length computed hash, or
|
||||
// parsed from a field which has the same length limitation. As such, an
|
||||
// error here is likely to only be caused during development.
|
||||
//
|
||||
// NOTE: another reasonable approach here might be to return a
|
||||
// randomized slice if we encounter an error, which would break the
|
||||
// connection, but avoid panicking. This would perhaps be safer but
|
||||
// significantly more confusing to users.
|
||||
panic(fmt.Errorf("failed to construct HKDF label: %s", err))
|
||||
}
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(c.hash.New, secret, hkdfLabelBytes).Read(out)
|
||||
if err != nil || n != length {
|
||||
panic("tls: HKDF-Expand-Label invocation failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// deriveSecret implements Derive-Secret from RFC 8446, Section 7.1.
|
||||
func (c *cipherSuiteTLS13) deriveSecret(secret []byte, label string, transcript hash.Hash) []byte {
|
||||
if transcript == nil {
|
||||
transcript = c.hash.New()
|
||||
}
|
||||
return c.expandLabel(secret, label, transcript.Sum(nil), c.hash.Size())
|
||||
}
|
||||
|
||||
// extract implements HKDF-Extract with the cipher suite hash.
|
||||
func (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte {
|
||||
if newSecret == nil {
|
||||
newSecret = make([]byte, c.hash.Size())
|
||||
}
|
||||
return hkdf.Extract(c.hash.New, newSecret, currentSecret)
|
||||
}
|
||||
|
||||
// nextTrafficSecret generates the next traffic secret, given the current one,
|
||||
// according to RFC 8446, Section 7.2.
|
||||
func (c *cipherSuiteTLS13) nextTrafficSecret(trafficSecret []byte) []byte {
|
||||
return c.expandLabel(trafficSecret, trafficUpdateLabel, nil, c.hash.Size())
|
||||
}
|
||||
|
||||
// trafficKey generates traffic keys according to RFC 8446, Section 7.3.
|
||||
func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
|
||||
key = c.expandLabel(trafficSecret, "key", nil, c.keyLen)
|
||||
iv = c.expandLabel(trafficSecret, "iv", nil, aeadNonceLength)
|
||||
return
|
||||
}
|
||||
|
||||
// finishedHash generates the Finished verify_data or PskBinderEntry according
|
||||
// to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey
|
||||
// selection.
|
||||
func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {
|
||||
finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size())
|
||||
verifyData := hmac.New(c.hash.New, finishedKey)
|
||||
verifyData.Write(transcript.Sum(nil))
|
||||
return verifyData.Sum(nil)
|
||||
}
|
||||
|
||||
// exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to
|
||||
// RFC 8446, Section 7.5.
|
||||
func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
|
||||
expMasterSecret := c.deriveSecret(masterSecret, exporterLabel, transcript)
|
||||
return func(label string, context []byte, length int) ([]byte, error) {
|
||||
secret := c.deriveSecret(expMasterSecret, label, nil)
|
||||
h := c.hash.New()
|
||||
h.Write(context)
|
||||
return c.expandLabel(secret, "exporter", h.Sum(nil), length), nil
|
||||
}
|
||||
}
|
||||
|
||||
// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman
|
||||
// according to RFC 8446, Section 4.2.8.2.
|
||||
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
|
||||
curve, ok := curveForCurveID(curveID)
|
||||
if !ok {
|
||||
return nil, errors.New("tls: internal error: unsupported curve")
|
||||
}
|
||||
|
||||
return curve.GenerateKey(rand)
|
||||
}
|
||||
|
||||
func curveForCurveID(id CurveID) (ecdh.Curve, bool) {
|
||||
switch id {
|
||||
case X25519:
|
||||
return ecdh.X25519(), true
|
||||
case CurveP256:
|
||||
return ecdh.P256(), true
|
||||
case CurveP384:
|
||||
return ecdh.P384(), true
|
||||
case CurveP521:
|
||||
return ecdh.P521(), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func curveIDForCurve(curve ecdh.Curve) (CurveID, bool) {
|
||||
switch curve {
|
||||
case ecdh.X25519():
|
||||
return X25519, true
|
||||
case ecdh.P256():
|
||||
return CurveP256, true
|
||||
case ecdh.P384():
|
||||
return CurveP384, true
|
||||
case ecdh.P521():
|
||||
return CurveP521, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
func needFIPS() bool { return false }
|
||||
|
||||
func supportedSignatureAlgorithms() []SignatureScheme {
|
||||
return defaultSupportedSignatureAlgorithms
|
||||
}
|
||||
|
||||
func fipsMinVersion(c *config) uint16 { panic("fipsMinVersion") }
|
||||
func fipsMaxVersion(c *config) uint16 { panic("fipsMaxVersion") }
|
||||
func fipsCurvePreferences(c *config) []CurveID { panic("fipsCurvePreferences") }
|
||||
func fipsCipherSuites(c *config) []uint16 { panic("fipsCipherSuites") }
|
||||
|
||||
var fipsSupportedSignatureAlgorithms []SignatureScheme
|
|
@ -1,283 +0,0 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Split a premaster secret in two as specified in RFC 4346, Section 5.
|
||||
func splitPreMasterSecret(secret []byte) (s1, s2 []byte) {
|
||||
s1 = secret[0 : (len(secret)+1)/2]
|
||||
s2 = secret[len(secret)/2:]
|
||||
return
|
||||
}
|
||||
|
||||
// pHash implements the P_hash function, as defined in RFC 4346, Section 5.
|
||||
func pHash(result, secret, seed []byte, hash func() hash.Hash) {
|
||||
h := hmac.New(hash, secret)
|
||||
h.Write(seed)
|
||||
a := h.Sum(nil)
|
||||
|
||||
j := 0
|
||||
for j < len(result) {
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
h.Write(seed)
|
||||
b := h.Sum(nil)
|
||||
copy(result[j:], b)
|
||||
j += len(b)
|
||||
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
a = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, Section 5.
|
||||
func prf10(result, secret, label, seed []byte) {
|
||||
hashSHA1 := sha1.New
|
||||
hashMD5 := md5.New
|
||||
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
|
||||
s1, s2 := splitPreMasterSecret(secret)
|
||||
pHash(result, s1, labelAndSeed, hashMD5)
|
||||
result2 := make([]byte, len(result))
|
||||
pHash(result2, s2, labelAndSeed, hashSHA1)
|
||||
|
||||
for i, b := range result2 {
|
||||
result[i] ^= b
|
||||
}
|
||||
}
|
||||
|
||||
// prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, Section 5.
|
||||
func prf12(hashFunc func() hash.Hash) func(result, secret, label, seed []byte) {
|
||||
return func(result, secret, label, seed []byte) {
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
|
||||
pHash(result, secret, labelAndSeed, hashFunc)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
masterSecretLength = 48 // Length of a master secret in TLS 1.1.
|
||||
finishedVerifyLength = 12 // Length of verify_data in a Finished message.
|
||||
)
|
||||
|
||||
var masterSecretLabel = []byte("master secret")
|
||||
var keyExpansionLabel = []byte("key expansion")
|
||||
var clientFinishedLabel = []byte("client finished")
|
||||
var serverFinishedLabel = []byte("server finished")
|
||||
|
||||
func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) {
|
||||
switch version {
|
||||
case VersionTLS10, VersionTLS11:
|
||||
return prf10, crypto.Hash(0)
|
||||
case VersionTLS12:
|
||||
if suite.flags&suiteSHA384 != 0 {
|
||||
return prf12(sha512.New384), crypto.SHA384
|
||||
}
|
||||
return prf12(sha256.New), crypto.SHA256
|
||||
default:
|
||||
panic("unknown version")
|
||||
}
|
||||
}
|
||||
|
||||
func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) {
|
||||
prf, _ := prfAndHashForVersion(version, suite)
|
||||
return prf
|
||||
}
|
||||
|
||||
// masterFromPreMasterSecret generates the master secret from the pre-master
|
||||
// secret. See RFC 5246, Section 8.1.
|
||||
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
|
||||
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
|
||||
seed = append(seed, clientRandom...)
|
||||
seed = append(seed, serverRandom...)
|
||||
|
||||
masterSecret := make([]byte, masterSecretLength)
|
||||
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed)
|
||||
return masterSecret
|
||||
}
|
||||
|
||||
// keysFromMasterSecret generates the connection keys from the master
|
||||
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
|
||||
// RFC 2246, Section 6.3.
|
||||
func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
|
||||
seed := make([]byte, 0, len(serverRandom)+len(clientRandom))
|
||||
seed = append(seed, serverRandom...)
|
||||
seed = append(seed, clientRandom...)
|
||||
|
||||
n := 2*macLen + 2*keyLen + 2*ivLen
|
||||
keyMaterial := make([]byte, n)
|
||||
prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed)
|
||||
clientMAC = keyMaterial[:macLen]
|
||||
keyMaterial = keyMaterial[macLen:]
|
||||
serverMAC = keyMaterial[:macLen]
|
||||
keyMaterial = keyMaterial[macLen:]
|
||||
clientKey = keyMaterial[:keyLen]
|
||||
keyMaterial = keyMaterial[keyLen:]
|
||||
serverKey = keyMaterial[:keyLen]
|
||||
keyMaterial = keyMaterial[keyLen:]
|
||||
clientIV = keyMaterial[:ivLen]
|
||||
keyMaterial = keyMaterial[ivLen:]
|
||||
serverIV = keyMaterial[:ivLen]
|
||||
return
|
||||
}
|
||||
|
||||
func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
|
||||
var buffer []byte
|
||||
if version >= VersionTLS12 {
|
||||
buffer = []byte{}
|
||||
}
|
||||
|
||||
prf, hash := prfAndHashForVersion(version, cipherSuite)
|
||||
if hash != 0 {
|
||||
return finishedHash{hash.New(), hash.New(), nil, nil, buffer, version, prf}
|
||||
}
|
||||
|
||||
return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), buffer, version, prf}
|
||||
}
|
||||
|
||||
// A finishedHash calculates the hash of a set of handshake messages suitable
|
||||
// for including in a Finished message.
|
||||
type finishedHash struct {
|
||||
client hash.Hash
|
||||
server hash.Hash
|
||||
|
||||
// Prior to TLS 1.2, an additional MD5 hash is required.
|
||||
clientMD5 hash.Hash
|
||||
serverMD5 hash.Hash
|
||||
|
||||
// In TLS 1.2, a full buffer is sadly required.
|
||||
buffer []byte
|
||||
|
||||
version uint16
|
||||
prf func(result, secret, label, seed []byte)
|
||||
}
|
||||
|
||||
func (h *finishedHash) Write(msg []byte) (n int, err error) {
|
||||
h.client.Write(msg)
|
||||
h.server.Write(msg)
|
||||
|
||||
if h.version < VersionTLS12 {
|
||||
h.clientMD5.Write(msg)
|
||||
h.serverMD5.Write(msg)
|
||||
}
|
||||
|
||||
if h.buffer != nil {
|
||||
h.buffer = append(h.buffer, msg...)
|
||||
}
|
||||
|
||||
return len(msg), nil
|
||||
}
|
||||
|
||||
func (h finishedHash) Sum() []byte {
|
||||
if h.version >= VersionTLS12 {
|
||||
return h.client.Sum(nil)
|
||||
}
|
||||
|
||||
out := make([]byte, 0, md5.Size+sha1.Size)
|
||||
out = h.clientMD5.Sum(out)
|
||||
return h.client.Sum(out)
|
||||
}
|
||||
|
||||
// clientSum returns the contents of the verify_data member of a client's
|
||||
// Finished message.
|
||||
func (h finishedHash) clientSum(masterSecret []byte) []byte {
|
||||
out := make([]byte, finishedVerifyLength)
|
||||
h.prf(out, masterSecret, clientFinishedLabel, h.Sum())
|
||||
return out
|
||||
}
|
||||
|
||||
// serverSum returns the contents of the verify_data member of a server's
|
||||
// Finished message.
|
||||
func (h finishedHash) serverSum(masterSecret []byte) []byte {
|
||||
out := make([]byte, finishedVerifyLength)
|
||||
h.prf(out, masterSecret, serverFinishedLabel, h.Sum())
|
||||
return out
|
||||
}
|
||||
|
||||
// hashForClientCertificate returns the handshake messages so far, pre-hashed if
|
||||
// necessary, suitable for signing by a TLS client certificate.
|
||||
func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte {
|
||||
if (h.version >= VersionTLS12 || sigType == signatureEd25519) && h.buffer == nil {
|
||||
panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer")
|
||||
}
|
||||
|
||||
if sigType == signatureEd25519 {
|
||||
return h.buffer
|
||||
}
|
||||
|
||||
if h.version >= VersionTLS12 {
|
||||
hash := hashAlg.New()
|
||||
hash.Write(h.buffer)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
if sigType == signatureECDSA {
|
||||
return h.server.Sum(nil)
|
||||
}
|
||||
|
||||
return h.Sum()
|
||||
}
|
||||
|
||||
// discardHandshakeBuffer is called when there is no more need to
|
||||
// buffer the entirety of the handshake messages.
|
||||
func (h *finishedHash) discardHandshakeBuffer() {
|
||||
h.buffer = nil
|
||||
}
|
||||
|
||||
// noExportedKeyingMaterial is used as a value of
|
||||
// ConnectionState.ekm when renegotiation is enabled and thus
|
||||
// we wish to fail all key-material export requests.
|
||||
func noExportedKeyingMaterial(label string, context []byte, length int) ([]byte, error) {
|
||||
return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when renegotiation is enabled")
|
||||
}
|
||||
|
||||
// ekmFromMasterSecret generates exported keying material as defined in RFC 5705.
|
||||
func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte) func(string, []byte, int) ([]byte, error) {
|
||||
return func(label string, context []byte, length int) ([]byte, error) {
|
||||
switch label {
|
||||
case "client finished", "server finished", "master secret", "key expansion":
|
||||
// These values are reserved and may not be used.
|
||||
return nil, fmt.Errorf("crypto/tls: reserved ExportKeyingMaterial label: %s", label)
|
||||
}
|
||||
|
||||
seedLen := len(serverRandom) + len(clientRandom)
|
||||
if context != nil {
|
||||
seedLen += 2 + len(context)
|
||||
}
|
||||
seed := make([]byte, 0, seedLen)
|
||||
|
||||
seed = append(seed, clientRandom...)
|
||||
seed = append(seed, serverRandom...)
|
||||
|
||||
if context != nil {
|
||||
if len(context) >= 1<<16 {
|
||||
return nil, fmt.Errorf("crypto/tls: ExportKeyingMaterial context too long")
|
||||
}
|
||||
seed = append(seed, byte(len(context)>>8), byte(len(context)))
|
||||
seed = append(seed, context...)
|
||||
}
|
||||
|
||||
keyMaterial := make([]byte, length)
|
||||
prfForVersion(version, suite)(keyMaterial, masterSecret, []byte(label), seed)
|
||||
return keyMaterial, nil
|
||||
}
|
||||
}
|
|
@ -1,412 +0,0 @@
|
|||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// QUICEncryptionLevel represents a QUIC encryption level used to transmit
|
||||
// handshake messages.
|
||||
type QUICEncryptionLevel int
|
||||
|
||||
const (
|
||||
QUICEncryptionLevelInitial = QUICEncryptionLevel(iota)
|
||||
QUICEncryptionLevelEarly
|
||||
QUICEncryptionLevelHandshake
|
||||
QUICEncryptionLevelApplication
|
||||
)
|
||||
|
||||
func (l QUICEncryptionLevel) String() string {
|
||||
switch l {
|
||||
case QUICEncryptionLevelInitial:
|
||||
return "Initial"
|
||||
case QUICEncryptionLevelEarly:
|
||||
return "Early"
|
||||
case QUICEncryptionLevelHandshake:
|
||||
return "Handshake"
|
||||
case QUICEncryptionLevelApplication:
|
||||
return "Application"
|
||||
default:
|
||||
return fmt.Sprintf("QUICEncryptionLevel(%v)", int(l))
|
||||
}
|
||||
}
|
||||
|
||||
// A QUICConn represents a connection which uses a QUIC implementation as the underlying
|
||||
// transport as described in RFC 9001.
|
||||
//
|
||||
// Methods of QUICConn are not safe for concurrent use.
|
||||
type QUICConn struct {
|
||||
conn *Conn
|
||||
|
||||
sessionTicketSent bool
|
||||
}
|
||||
|
||||
// A QUICConfig configures a QUICConn.
|
||||
type QUICConfig struct {
|
||||
TLSConfig *Config
|
||||
ExtraConfig *ExtraConfig
|
||||
}
|
||||
|
||||
// A QUICEventKind is a type of operation on a QUIC connection.
|
||||
type QUICEventKind int
|
||||
|
||||
const (
|
||||
// QUICNoEvent indicates that there are no events available.
|
||||
QUICNoEvent QUICEventKind = iota
|
||||
|
||||
// QUICSetReadSecret and QUICSetWriteSecret provide the read and write
|
||||
// secrets for a given encryption level.
|
||||
// QUICEvent.Level, QUICEvent.Data, and QUICEvent.Suite are set.
|
||||
//
|
||||
// Secrets for the Initial encryption level are derived from the initial
|
||||
// destination connection ID, and are not provided by the QUICConn.
|
||||
QUICSetReadSecret
|
||||
QUICSetWriteSecret
|
||||
|
||||
// QUICWriteData provides data to send to the peer in CRYPTO frames.
|
||||
// QUICEvent.Data is set.
|
||||
QUICWriteData
|
||||
|
||||
// QUICTransportParameters provides the peer's QUIC transport parameters.
|
||||
// QUICEvent.Data is set.
|
||||
QUICTransportParameters
|
||||
|
||||
// QUICTransportParametersRequired indicates that the caller must provide
|
||||
// QUIC transport parameters to send to the peer. The caller should set
|
||||
// the transport parameters with QUICConn.SetTransportParameters and call
|
||||
// QUICConn.NextEvent again.
|
||||
//
|
||||
// If transport parameters are set before calling QUICConn.Start, the
|
||||
// connection will never generate a QUICTransportParametersRequired event.
|
||||
QUICTransportParametersRequired
|
||||
|
||||
// QUICRejectedEarlyData indicates that the server rejected 0-RTT data even
|
||||
// if we offered it. It's returned before QUICEncryptionLevelApplication
|
||||
// keys are returned.
|
||||
QUICRejectedEarlyData
|
||||
|
||||
// QUICHandshakeDone indicates that the TLS handshake has completed.
|
||||
QUICHandshakeDone
|
||||
)
|
||||
|
||||
// A QUICEvent is an event occurring on a QUIC connection.
|
||||
//
|
||||
// The type of event is specified by the Kind field.
|
||||
// The contents of the other fields are kind-specific.
|
||||
type QUICEvent struct {
|
||||
Kind QUICEventKind
|
||||
|
||||
// Set for QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData.
|
||||
Level QUICEncryptionLevel
|
||||
|
||||
// Set for QUICTransportParameters, QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData.
|
||||
// The contents are owned by crypto/tls, and are valid until the next NextEvent call.
|
||||
Data []byte
|
||||
|
||||
// Set for QUICSetReadSecret and QUICSetWriteSecret.
|
||||
Suite uint16
|
||||
}
|
||||
|
||||
type quicState struct {
|
||||
events []QUICEvent
|
||||
nextEvent int
|
||||
|
||||
// eventArr is a statically allocated event array, large enough to handle
|
||||
// the usual maximum number of events resulting from a single call: transport
|
||||
// parameters, Initial data, Early read secret, Handshake write and read
|
||||
// secrets, Handshake data, Application write secret, Application data.
|
||||
eventArr [8]QUICEvent
|
||||
|
||||
started bool
|
||||
signalc chan struct{} // handshake data is available to be read
|
||||
blockedc chan struct{} // handshake is waiting for data, closed when done
|
||||
cancelc <-chan struct{} // handshake has been canceled
|
||||
cancel context.CancelFunc
|
||||
|
||||
// readbuf is shared between HandleData and the handshake goroutine.
|
||||
// HandshakeCryptoData passes ownership to the handshake goroutine by
|
||||
// reading from signalc, and reclaims ownership by reading from blockedc.
|
||||
readbuf []byte
|
||||
|
||||
transportParams []byte // to send to the peer
|
||||
}
|
||||
|
||||
// QUICClient returns a new TLS client side connection using QUICTransport as the
|
||||
// underlying transport. The config cannot be nil.
|
||||
//
|
||||
// The config's MinVersion must be at least TLS 1.3.
|
||||
func QUICClient(config *QUICConfig) *QUICConn {
|
||||
return newQUICConn(Client(nil, config.TLSConfig), config.ExtraConfig)
|
||||
}
|
||||
|
||||
// QUICServer returns a new TLS server side connection using QUICTransport as the
|
||||
// underlying transport. The config cannot be nil.
|
||||
//
|
||||
// The config's MinVersion must be at least TLS 1.3.
|
||||
func QUICServer(config *QUICConfig) *QUICConn {
|
||||
return newQUICConn(Server(nil, config.TLSConfig), config.ExtraConfig)
|
||||
}
|
||||
|
||||
func newQUICConn(conn *Conn, extraConfig *ExtraConfig) *QUICConn {
|
||||
conn.quic = &quicState{
|
||||
signalc: make(chan struct{}),
|
||||
blockedc: make(chan struct{}),
|
||||
}
|
||||
conn.quic.events = conn.quic.eventArr[:0]
|
||||
conn.extraConfig = extraConfig
|
||||
return &QUICConn{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the client or server handshake protocol.
|
||||
// It may produce connection events, which may be read with NextEvent.
|
||||
//
|
||||
// Start must be called at most once.
|
||||
func (q *QUICConn) Start(ctx context.Context) error {
|
||||
if q.conn.quic.started {
|
||||
return quicError(errors.New("tls: Start called more than once"))
|
||||
}
|
||||
q.conn.quic.started = true
|
||||
if q.conn.config.MinVersion < VersionTLS13 {
|
||||
return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.13"))
|
||||
}
|
||||
go q.conn.HandshakeContext(ctx)
|
||||
if _, ok := <-q.conn.quic.blockedc; !ok {
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextEvent returns the next event occurring on the connection.
|
||||
// It returns an event with a Kind of QUICNoEvent when no events are available.
|
||||
func (q *QUICConn) NextEvent() QUICEvent {
|
||||
qs := q.conn.quic
|
||||
if last := qs.nextEvent - 1; last >= 0 && len(qs.events[last].Data) > 0 {
|
||||
// Write over some of the previous event's data,
|
||||
// to catch callers erroniously retaining it.
|
||||
qs.events[last].Data[0] = 0
|
||||
}
|
||||
if qs.nextEvent >= len(qs.events) {
|
||||
qs.events = qs.events[:0]
|
||||
qs.nextEvent = 0
|
||||
return QUICEvent{Kind: QUICNoEvent}
|
||||
}
|
||||
e := qs.events[qs.nextEvent]
|
||||
qs.events[qs.nextEvent] = QUICEvent{} // zero out references to data
|
||||
qs.nextEvent++
|
||||
return e
|
||||
}
|
||||
|
||||
// Close closes the connection and stops any in-progress handshake.
|
||||
func (q *QUICConn) Close() error {
|
||||
if q.conn.quic.cancel == nil {
|
||||
return nil // never started
|
||||
}
|
||||
q.conn.quic.cancel()
|
||||
for range q.conn.quic.blockedc {
|
||||
// Wait for the handshake goroutine to return.
|
||||
}
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
|
||||
// HandleData handles handshake bytes received from the peer.
|
||||
// It may produce connection events, which may be read with NextEvent.
|
||||
func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error {
|
||||
c := q.conn
|
||||
if c.in.level != level {
|
||||
return quicError(c.in.setErrorLocked(errors.New("tls: handshake data received at wrong level")))
|
||||
}
|
||||
c.quic.readbuf = data
|
||||
<-c.quic.signalc
|
||||
_, ok := <-c.quic.blockedc
|
||||
if ok {
|
||||
// The handshake goroutine is waiting for more data.
|
||||
return nil
|
||||
}
|
||||
// The handshake goroutine has exited.
|
||||
c.hand.Write(c.quic.readbuf)
|
||||
c.quic.readbuf = nil
|
||||
for q.conn.hand.Len() >= 4 && q.conn.handshakeErr == nil {
|
||||
b := q.conn.hand.Bytes()
|
||||
n := int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||
if 4+n < len(b) {
|
||||
return nil
|
||||
}
|
||||
if err := q.conn.handlePostHandshakeMessage(); err != nil {
|
||||
return quicError(err)
|
||||
}
|
||||
}
|
||||
if q.conn.handshakeErr != nil {
|
||||
return quicError(q.conn.handshakeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendSessionTicket sends a session ticket to the client.
|
||||
// It produces connection events, which may be read with NextEvent.
|
||||
// Currently, it can only be called once.
|
||||
func (q *QUICConn) SendSessionTicket(earlyData bool) error {
|
||||
c := q.conn
|
||||
if !c.isHandshakeComplete.Load() {
|
||||
return quicError(errors.New("tls: SendSessionTicket called before handshake completed"))
|
||||
}
|
||||
if c.isClient {
|
||||
return quicError(errors.New("tls: SendSessionTicket called on the client"))
|
||||
}
|
||||
if q.sessionTicketSent {
|
||||
return quicError(errors.New("tls: SendSessionTicket called multiple times"))
|
||||
}
|
||||
q.sessionTicketSent = true
|
||||
return quicError(c.sendSessionTicket(earlyData))
|
||||
}
|
||||
|
||||
// ConnectionState returns basic TLS details about the connection.
|
||||
func (q *QUICConn) ConnectionState() ConnectionState {
|
||||
return q.conn.ConnectionState()
|
||||
}
|
||||
|
||||
// SetTransportParameters sets the transport parameters to send to the peer.
|
||||
//
|
||||
// Server connections may delay setting the transport parameters until after
|
||||
// receiving the client's transport parameters. See QUICTransportParametersRequired.
|
||||
func (q *QUICConn) SetTransportParameters(params []byte) {
|
||||
if params == nil {
|
||||
params = []byte{}
|
||||
}
|
||||
q.conn.quic.transportParams = params
|
||||
if q.conn.quic.started {
|
||||
<-q.conn.quic.signalc
|
||||
<-q.conn.quic.blockedc
|
||||
}
|
||||
}
|
||||
|
||||
// quicError ensures err is an AlertError.
|
||||
// If err is not already, quicError wraps it with alertInternalError.
|
||||
func quicError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var ae AlertError
|
||||
if errors.As(err, &ae) {
|
||||
return err
|
||||
}
|
||||
var a alert
|
||||
if !errors.As(err, &a) {
|
||||
a = alertInternalError
|
||||
}
|
||||
// Return an error wrapping the original error and an AlertError.
|
||||
// Truncate the text of the alert to 0 characters.
|
||||
return fmt.Errorf("%w%.0w", err, AlertError(a))
|
||||
}
|
||||
|
||||
func (c *Conn) quicReadHandshakeBytes(n int) error {
|
||||
for c.hand.Len() < n {
|
||||
if err := c.quicWaitForSignal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICSetReadSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetWriteSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICSetWriteSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicWriteCryptoData(level QUICEncryptionLevel, data []byte) {
|
||||
var last *QUICEvent
|
||||
if len(c.quic.events) > 0 {
|
||||
last = &c.quic.events[len(c.quic.events)-1]
|
||||
}
|
||||
if last == nil || last.Kind != QUICWriteData || last.Level != level {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICWriteData,
|
||||
Level: level,
|
||||
})
|
||||
last = &c.quic.events[len(c.quic.events)-1]
|
||||
}
|
||||
last.Data = append(last.Data, data...)
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetTransportParameters(params []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICTransportParameters,
|
||||
Data: params,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicGetTransportParameters() ([]byte, error) {
|
||||
if c.quic.transportParams == nil {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICTransportParametersRequired,
|
||||
})
|
||||
}
|
||||
for c.quic.transportParams == nil {
|
||||
if err := c.quicWaitForSignal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.quic.transportParams, nil
|
||||
}
|
||||
|
||||
func (c *Conn) quicHandshakeComplete() {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICHandshakeDone,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicRejectedEarlyData() {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICRejectedEarlyData,
|
||||
})
|
||||
}
|
||||
|
||||
// quicWaitForSignal notifies the QUICConn that handshake progress is blocked,
|
||||
// and waits for a signal that the handshake should proceed.
|
||||
//
|
||||
// The handshake may become blocked waiting for handshake bytes
|
||||
// or for the user to provide transport parameters.
|
||||
func (c *Conn) quicWaitForSignal() error {
|
||||
// Drop the handshake mutex while blocked to allow the user
|
||||
// to call ConnectionState before the handshake completes.
|
||||
c.handshakeMutex.Unlock()
|
||||
defer c.handshakeMutex.Lock()
|
||||
// Send on blockedc to notify the QUICConn that the handshake is blocked.
|
||||
// Exported methods of QUICConn wait for the handshake to become blocked
|
||||
// before returning to the user.
|
||||
select {
|
||||
case c.quic.blockedc <- struct{}{}:
|
||||
case <-c.quic.cancelc:
|
||||
return c.sendAlertLocked(alertCloseNotify)
|
||||
}
|
||||
// The QUICConn reads from signalc to notify us that the handshake may
|
||||
// be able to proceed. (The QUICConn reads, because we close signalc to
|
||||
// indicate that the handshake has completed.)
|
||||
select {
|
||||
case c.quic.signalc <- struct{}{}:
|
||||
c.hand.Write(c.quic.readbuf)
|
||||
c.quic.readbuf = nil
|
||||
case <-c.quic.cancelc:
|
||||
return c.sendAlertLocked(alertCloseNotify)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package qtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"io"
|
||||
)
|
||||
|
||||
// sessionState contains the information that is serialized into a session
|
||||
// ticket in order to later resume a connection.
|
||||
type sessionState struct {
|
||||
vers uint16
|
||||
cipherSuite uint16
|
||||
createdAt uint64
|
||||
masterSecret []byte // opaque master_secret<1..2^16-1>;
|
||||
// struct { opaque certificate<1..2^24-1> } Certificate;
|
||||
certificates [][]byte // Certificate certificate_list<0..2^24-1>;
|
||||
|
||||
// usedOldKey is true if the ticket from which this session came from
|
||||
// was encrypted with an older key and thus should be refreshed.
|
||||
usedOldKey bool
|
||||
}
|
||||
|
||||
func (m *sessionState) marshal() ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(m.vers)
|
||||
b.AddUint16(m.cipherSuite)
|
||||
addUint64(&b, m.createdAt)
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(m.masterSecret)
|
||||
})
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, cert := range m.certificates {
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(cert)
|
||||
})
|
||||
}
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func (m *sessionState) unmarshal(data []byte) bool {
|
||||
*m = sessionState{usedOldKey: m.usedOldKey}
|
||||
s := cryptobyte.String(data)
|
||||
if ok := s.ReadUint16(&m.vers) &&
|
||||
s.ReadUint16(&m.cipherSuite) &&
|
||||
readUint64(&s, &m.createdAt) &&
|
||||
readUint16LengthPrefixed(&s, &m.masterSecret) &&
|
||||
len(m.masterSecret) != 0; !ok {
|
||||
return false
|
||||
}
|
||||
var certList cryptobyte.String
|
||||
if !s.ReadUint24LengthPrefixed(&certList) {
|
||||
return false
|
||||
}
|
||||
for !certList.Empty() {
|
||||
var cert []byte
|
||||
if !readUint24LengthPrefixed(&certList, &cert) {
|
||||
return false
|
||||
}
|
||||
m.certificates = append(m.certificates, cert)
|
||||
}
|
||||
return s.Empty()
|
||||
}
|
||||
|
||||
// sessionStateTLS13 is the content of a TLS 1.3 session ticket. Its first
|
||||
// version (revision = 0) doesn't carry any of the information needed for 0-RTT
|
||||
// validation and the nonce is always empty.
|
||||
// version (revision = 1) carries the max_early_data_size sent in the ticket.
|
||||
// version (revision = 2) carries the ALPN sent in the ticket.
|
||||
type sessionStateTLS13 struct {
|
||||
// uint8 version = 0x0304;
|
||||
// uint8 revision = 2;
|
||||
cipherSuite uint16
|
||||
createdAt uint64
|
||||
resumptionSecret []byte // opaque resumption_master_secret<1..2^8-1>;
|
||||
certificate Certificate // CertificateEntry certificate_list<0..2^24-1>;
|
||||
maxEarlyData uint32
|
||||
alpn string
|
||||
|
||||
appData []byte
|
||||
}
|
||||
|
||||
func (m *sessionStateTLS13) marshal() ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(VersionTLS13)
|
||||
b.AddUint8(2) // revision
|
||||
b.AddUint16(m.cipherSuite)
|
||||
addUint64(&b, m.createdAt)
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(m.resumptionSecret)
|
||||
})
|
||||
marshalCertificate(&b, m.certificate)
|
||||
b.AddUint32(m.maxEarlyData)
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes([]byte(m.alpn))
|
||||
})
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(m.appData)
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func (m *sessionStateTLS13) unmarshal(data []byte) bool {
|
||||
*m = sessionStateTLS13{}
|
||||
s := cryptobyte.String(data)
|
||||
var version uint16
|
||||
var revision uint8
|
||||
var alpn []byte
|
||||
ret := s.ReadUint16(&version) &&
|
||||
version == VersionTLS13 &&
|
||||
s.ReadUint8(&revision) &&
|
||||
revision == 2 &&
|
||||
s.ReadUint16(&m.cipherSuite) &&
|
||||
readUint64(&s, &m.createdAt) &&
|
||||
readUint8LengthPrefixed(&s, &m.resumptionSecret) &&
|
||||
len(m.resumptionSecret) != 0 &&
|
||||
unmarshalCertificate(&s, &m.certificate) &&
|
||||
s.ReadUint32(&m.maxEarlyData) &&
|
||||
readUint8LengthPrefixed(&s, &alpn) &&
|
||||
readUint16LengthPrefixed(&s, &m.appData) &&
|
||||
s.Empty()
|
||||
m.alpn = string(alpn)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Conn) encryptTicket(state []byte) ([]byte, error) {
|
||||
if len(c.ticketKeys) == 0 {
|
||||
return nil, errors.New("tls: internal error: session ticket keys unavailable")
|
||||
}
|
||||
|
||||
encrypted := make([]byte, ticketKeyNameLen+aes.BlockSize+len(state)+sha256.Size)
|
||||
keyName := encrypted[:ticketKeyNameLen]
|
||||
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
||||
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
||||
|
||||
if _, err := io.ReadFull(c.config.rand(), iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := c.ticketKeys[0]
|
||||
copy(keyName, key.keyName[:])
|
||||
block, err := aes.NewCipher(key.aesKey[:])
|
||||
if err != nil {
|
||||
return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error())
|
||||
}
|
||||
cipher.NewCTR(block, iv).XORKeyStream(encrypted[ticketKeyNameLen+aes.BlockSize:], state)
|
||||
|
||||
mac := hmac.New(sha256.New, key.hmacKey[:])
|
||||
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
||||
mac.Sum(macBytes[:0])
|
||||
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey bool) {
|
||||
if len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
keyName := encrypted[:ticketKeyNameLen]
|
||||
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
||||
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
||||
ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size]
|
||||
|
||||
keyIndex := -1
|
||||
for i, candidateKey := range c.ticketKeys {
|
||||
if bytes.Equal(keyName, candidateKey.keyName[:]) {
|
||||
keyIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if keyIndex == -1 {
|
||||
return nil, false
|
||||
}
|
||||
key := &c.ticketKeys[keyIndex]
|
||||
|
||||
mac := hmac.New(sha256.New, key.hmacKey[:])
|
||||
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
||||
expected := mac.Sum(nil)
|
||||
|
||||
if subtle.ConstantTimeCompare(macBytes, expected) != 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key.aesKey[:])
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
plaintext = make([]byte, len(ciphertext))
|
||||
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext, keyIndex > 0
|
||||
}
|
|
@ -1,356 +0,0 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// package qtls partially implements TLS 1.2, as specified in RFC 5246,
|
||||
// and TLS 1.3, as specified in RFC 8446.
|
||||
package qtls
|
||||
|
||||
// BUG(agl): The crypto/tls package only implements some countermeasures
|
||||
// against Lucky13 attacks on CBC-mode encryption, and only on SHA1
|
||||
// variants. See http://www.isg.rhul.ac.uk/tls/TLStiming.pdf and
|
||||
// https://www.imperialviolet.org/2013/02/04/luckythirteen.html.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server returns a new TLS server side connection
|
||||
// using conn as the underlying transport.
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func Server(conn net.Conn, config *Config) *Conn {
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
config: fromConfig(config),
|
||||
}
|
||||
c.handshakeFn = c.serverHandshake
|
||||
return c
|
||||
}
|
||||
|
||||
// Client returns a new TLS client side connection
|
||||
// using conn as the underlying transport.
|
||||
// The config cannot be nil: users must set either ServerName or
|
||||
// InsecureSkipVerify in the config.
|
||||
func Client(conn net.Conn, config *Config) *Conn {
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
config: fromConfig(config),
|
||||
isClient: true,
|
||||
}
|
||||
c.handshakeFn = c.clientHandshake
|
||||
return c
|
||||
}
|
||||
|
||||
// A listener implements a network listener (net.Listener) for TLS connections.
|
||||
type listener struct {
|
||||
net.Listener
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next incoming TLS connection.
|
||||
// The returned connection is of type *Conn.
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Server(c, l.config), nil
|
||||
}
|
||||
|
||||
// NewListener creates a Listener which accepts connections from an inner
|
||||
// Listener and wraps each connection with Server.
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func NewListener(inner net.Listener, config *Config) net.Listener {
|
||||
l := new(listener)
|
||||
l.Listener = inner
|
||||
l.config = config
|
||||
return l
|
||||
}
|
||||
|
||||
// Listen creates a TLS listener accepting connections on the
|
||||
// given network address using net.Listen.
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func Listen(network, laddr string, config *Config) (net.Listener, error) {
|
||||
if config == nil || len(config.Certificates) == 0 &&
|
||||
config.GetCertificate == nil && config.GetConfigForClient == nil {
|
||||
return nil, errors.New("tls: neither Certificates, GetCertificate, nor GetConfigForClient set in Config")
|
||||
}
|
||||
l, err := net.Listen(network, laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewListener(l, config), nil
|
||||
}
|
||||
|
||||
type timeoutError struct{}
|
||||
|
||||
func (timeoutError) Error() string { return "tls: DialWithDialer timed out" }
|
||||
func (timeoutError) Timeout() bool { return true }
|
||||
func (timeoutError) Temporary() bool { return true }
|
||||
|
||||
// DialWithDialer connects to the given network address using dialer.Dial and
|
||||
// then initiates a TLS handshake, returning the resulting TLS connection. Any
|
||||
// timeout or deadline given in the dialer apply to connection and TLS
|
||||
// handshake as a whole.
|
||||
//
|
||||
// DialWithDialer interprets a nil configuration as equivalent to the zero
|
||||
// configuration; see the documentation of Config for the defaults.
|
||||
//
|
||||
// DialWithDialer uses context.Background internally; to specify the context,
|
||||
// use Dialer.DialContext with NetDialer set to the desired dialer.
|
||||
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
|
||||
return dial(context.Background(), dialer, network, addr, config)
|
||||
}
|
||||
|
||||
func dial(ctx context.Context, netDialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
|
||||
if netDialer.Timeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, netDialer.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if !netDialer.Deadline.IsZero() {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(ctx, netDialer.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
rawConn, err := netDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
hostname := addr[:colonPos]
|
||||
|
||||
if config == nil {
|
||||
config = defaultConfig()
|
||||
}
|
||||
// If no ServerName is set, infer the ServerName
|
||||
// from the hostname we're connecting to.
|
||||
if config.ServerName == "" {
|
||||
// Make a copy to avoid polluting argument or default.
|
||||
c := config.Clone()
|
||||
c.ServerName = hostname
|
||||
config = c
|
||||
}
|
||||
|
||||
conn := Client(rawConn, config)
|
||||
if err := conn.HandshakeContext(ctx); err != nil {
|
||||
rawConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Dial connects to the given network address using net.Dial
|
||||
// and then initiates a TLS handshake, returning the resulting
|
||||
// TLS connection.
|
||||
// Dial interprets a nil configuration as equivalent to
|
||||
// the zero configuration; see the documentation of Config
|
||||
// for the defaults.
|
||||
func Dial(network, addr string, config *Config) (*Conn, error) {
|
||||
return DialWithDialer(new(net.Dialer), network, addr, config)
|
||||
}
|
||||
|
||||
// Dialer dials TLS connections given a configuration and a Dialer for the
|
||||
// underlying connection.
|
||||
type Dialer struct {
|
||||
// NetDialer is the optional dialer to use for the TLS connections'
|
||||
// underlying TCP connections.
|
||||
// A nil NetDialer is equivalent to the net.Dialer zero value.
|
||||
NetDialer *net.Dialer
|
||||
|
||||
// Config is the TLS configuration to use for new connections.
|
||||
// A nil configuration is equivalent to the zero
|
||||
// configuration; see the documentation of Config for the
|
||||
// defaults.
|
||||
Config *Config
|
||||
}
|
||||
|
||||
// Dial connects to the given network address and initiates a TLS
|
||||
// handshake, returning the resulting TLS connection.
|
||||
//
|
||||
// The returned Conn, if any, will always be of type *Conn.
|
||||
//
|
||||
// Dial uses context.Background internally; to specify the context,
|
||||
// use DialContext.
|
||||
func (d *Dialer) Dial(network, addr string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
func (d *Dialer) netDialer() *net.Dialer {
|
||||
if d.NetDialer != nil {
|
||||
return d.NetDialer
|
||||
}
|
||||
return new(net.Dialer)
|
||||
}
|
||||
|
||||
// DialContext connects to the given network address and initiates a TLS
|
||||
// handshake, returning the resulting TLS connection.
|
||||
//
|
||||
// The provided Context must be non-nil. If the context expires before
|
||||
// the connection is complete, an error is returned. Once successfully
|
||||
// connected, any expiration of the context will not affect the
|
||||
// connection.
|
||||
//
|
||||
// The returned Conn, if any, will always be of type *Conn.
|
||||
func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
c, err := dial(ctx, d.netDialer(), network, addr, d.Config)
|
||||
if err != nil {
|
||||
// Don't return c (a typed nil) in an interface.
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// LoadX509KeyPair reads and parses a public/private key pair from a pair
|
||||
// of files. The files must contain PEM encoded data. The certificate file
|
||||
// may contain intermediate certificates following the leaf certificate to
|
||||
// form a certificate chain. On successful return, Certificate.Leaf will
|
||||
// be nil because the parsed form of the certificate is not retained.
|
||||
func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) {
|
||||
certPEMBlock, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
keyPEMBlock, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
return X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
}
|
||||
|
||||
// X509KeyPair parses a public/private key pair from a pair of
|
||||
// PEM encoded data. On successful return, Certificate.Leaf will be nil because
|
||||
// the parsed form of the certificate is not retained.
|
||||
func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||
fail := func(err error) (Certificate, error) { return Certificate{}, err }
|
||||
|
||||
var cert Certificate
|
||||
var skippedBlockTypes []string
|
||||
for {
|
||||
var certDERBlock *pem.Block
|
||||
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
|
||||
} else {
|
||||
skippedBlockTypes = append(skippedBlockTypes, certDERBlock.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cert.Certificate) == 0 {
|
||||
if len(skippedBlockTypes) == 0 {
|
||||
return fail(errors.New("tls: failed to find any PEM data in certificate input"))
|
||||
}
|
||||
if len(skippedBlockTypes) == 1 && strings.HasSuffix(skippedBlockTypes[0], "PRIVATE KEY") {
|
||||
return fail(errors.New("tls: failed to find certificate PEM data in certificate input, but did find a private key; PEM inputs may have been switched"))
|
||||
}
|
||||
return fail(fmt.Errorf("tls: failed to find \"CERTIFICATE\" PEM block in certificate input after skipping PEM blocks of the following types: %v", skippedBlockTypes))
|
||||
}
|
||||
|
||||
skippedBlockTypes = skippedBlockTypes[:0]
|
||||
var keyDERBlock *pem.Block
|
||||
for {
|
||||
keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)
|
||||
if keyDERBlock == nil {
|
||||
if len(skippedBlockTypes) == 0 {
|
||||
return fail(errors.New("tls: failed to find any PEM data in key input"))
|
||||
}
|
||||
if len(skippedBlockTypes) == 1 && skippedBlockTypes[0] == "CERTIFICATE" {
|
||||
return fail(errors.New("tls: found a certificate rather than a key in the PEM for the private key"))
|
||||
}
|
||||
return fail(fmt.Errorf("tls: failed to find PEM block with type ending in \"PRIVATE KEY\" in key input after skipping PEM blocks of the following types: %v", skippedBlockTypes))
|
||||
}
|
||||
if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") {
|
||||
break
|
||||
}
|
||||
skippedBlockTypes = append(skippedBlockTypes, keyDERBlock.Type)
|
||||
}
|
||||
|
||||
// We don't need to parse the public key for TLS, but we so do anyway
|
||||
// to check that it looks sane and matches the private key.
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
|
||||
cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
|
||||
switch pub := x509Cert.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if pub.N.Cmp(priv.N) != 0 {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
default:
|
||||
return fail(errors.New("tls: unknown public key algorithm"))
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
||||
// PKCS #1 private keys by default, while OpenSSL 1.0.0 generates PKCS #8 keys.
|
||||
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
||||
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
|
||||
return key, nil
|
||||
default:
|
||||
return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
|
||||
}
|
||||
}
|
||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("tls: failed to parse private key")
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package qtls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !structsEqual(&tls.ConnectionState{}, &connectionState{}) {
|
||||
panic("qtls.ConnectionState doesn't match")
|
||||
}
|
||||
if !structsEqual(&tls.ClientSessionState{}, &clientSessionState{}) {
|
||||
panic("qtls.ClientSessionState doesn't match")
|
||||
}
|
||||
if !structsEqual(&tls.CertificateRequestInfo{}, &certificateRequestInfo{}) {
|
||||
panic("qtls.CertificateRequestInfo doesn't match")
|
||||
}
|
||||
if !structsEqual(&tls.Config{}, &config{}) {
|
||||
panic("qtls.Config doesn't match")
|
||||
}
|
||||
if !structsEqual(&tls.ClientHelloInfo{}, &clientHelloInfo{}) {
|
||||
panic("qtls.ClientHelloInfo doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func toConnectionState(c connectionState) ConnectionState {
|
||||
return *(*ConnectionState)(unsafe.Pointer(&c))
|
||||
}
|
||||
|
||||
func toClientSessionState(s *clientSessionState) *ClientSessionState {
|
||||
return (*ClientSessionState)(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func fromClientSessionState(s *ClientSessionState) *clientSessionState {
|
||||
return (*clientSessionState)(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func toCertificateRequestInfo(i *certificateRequestInfo) *CertificateRequestInfo {
|
||||
return (*CertificateRequestInfo)(unsafe.Pointer(i))
|
||||
}
|
||||
|
||||
func toConfig(c *config) *Config {
|
||||
return (*Config)(unsafe.Pointer(c))
|
||||
}
|
||||
|
||||
func fromConfig(c *Config) *config {
|
||||
return (*config)(unsafe.Pointer(c))
|
||||
}
|
||||
|
||||
func toClientHelloInfo(chi *clientHelloInfo) *ClientHelloInfo {
|
||||
return (*ClientHelloInfo)(unsafe.Pointer(chi))
|
||||
}
|
||||
|
||||
func structsEqual(a, b interface{}) bool {
|
||||
return compare(reflect.ValueOf(a), reflect.ValueOf(b))
|
||||
}
|
||||
|
||||
func compare(a, b reflect.Value) bool {
|
||||
sa := a.Elem()
|
||||
sb := b.Elem()
|
||||
if sa.NumField() != sb.NumField() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < sa.NumField(); i++ {
|
||||
fa := sa.Type().Field(i)
|
||||
fb := sb.Type().Field(i)
|
||||
if !reflect.DeepEqual(fa.Index, fb.Index) || fa.Name != fb.Name || fa.Anonymous != fb.Anonymous || fa.Offset != fb.Offset || !reflect.DeepEqual(fa.Type, fb.Type) {
|
||||
if fa.Type.Kind() != fb.Type.Kind() {
|
||||
return false
|
||||
}
|
||||
if fa.Type.Kind() == reflect.Slice {
|
||||
if !compareStruct(fa.Type.Elem(), fb.Type.Elem()) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func compareStruct(a, b reflect.Type) bool {
|
||||
if a.NumField() != b.NumField() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < a.NumField(); i++ {
|
||||
fa := a.Field(i)
|
||||
fb := b.Field(i)
|
||||
if !reflect.DeepEqual(fa.Index, fb.Index) || fa.Name != fb.Name || fa.Anonymous != fb.Anonymous || fa.Offset != fb.Offset || !reflect.DeepEqual(fa.Type, fb.Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// InitSessionTicketKeys triggers the initialization of session ticket keys.
|
||||
func InitSessionTicketKeys(conf *Config) {
|
||||
fromConfig(conf).ticketKeys(nil)
|
||||
}
|
|
@ -2,19 +2,18 @@ run:
|
|||
skip-files:
|
||||
- internal/handshake/cipher_suite.go
|
||||
linters-settings:
|
||||
depguard:
|
||||
type: blacklist
|
||||
packages:
|
||||
- github.com/marten-seemann/qtls
|
||||
- github.com/quic-go/qtls-go1-19
|
||||
- github.com/quic-go/qtls-go1-20
|
||||
packages-with-error-message:
|
||||
- github.com/marten-seemann/qtls: "importing qtls only allowed in internal/qtls"
|
||||
- github.com/quic-go/qtls-go1-19: "importing qtls only allowed in internal/qtls"
|
||||
- github.com/quic-go/qtls-go1-20: "importing qtls only allowed in internal/qtls"
|
||||
misspell:
|
||||
ignore-words:
|
||||
- ect
|
||||
depguard:
|
||||
rules:
|
||||
quicvarint:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/github.com/quic-go/quic-go/quicvarint/*"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
|
|
|
@ -2,211 +2,38 @@
|
|||
|
||||
<img src="docs/quic.png" width=303 height=124>
|
||||
|
||||
[![Documentation](https://img.shields.io/badge/docs-quic--go.net-red?style=flat)](https://quic-go.net/docs/)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go)](https://pkg.go.dev/github.com/quic-go/quic-go)
|
||||
[![Code Coverage](https://img.shields.io/codecov/c/github/quic-go/quic-go/master.svg?style=flat-square)](https://codecov.io/gh/quic-go/quic-go/)
|
||||
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go)
|
||||
|
||||
quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go. It has support for HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
|
||||
quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go. It has support for HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) and HTTP Datagrams ([RFC 9297](https://datatracker.ietf.org/doc/html/rfc9297)).
|
||||
|
||||
In addition to these base RFCs, it also implements the following RFCs:
|
||||
* Unreliable Datagram Extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221))
|
||||
* Datagram Packetization Layer Path MTU Discovery (DPLPMTUD, [RFC 8899](https://datatracker.ietf.org/doc/html/rfc8899))
|
||||
* QUIC Version 2 ([RFC 9369](https://datatracker.ietf.org/doc/html/rfc9369))
|
||||
* QUIC Event Logging using qlog ([draft-ietf-quic-qlog-main-schema](https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-main-schema/) and [draft-ietf-quic-qlog-quic-events](https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-quic-events/))
|
||||
|
||||
## Using QUIC
|
||||
Support for WebTransport over HTTP/3 ([draft-ietf-webtrans-http3](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/)) is implemented in [webtransport-go](https://github.com/quic-go/webtransport-go).
|
||||
|
||||
### Running a Server
|
||||
|
||||
The central entry point is the `quic.Transport`. A transport manages QUIC connections running on a single UDP socket. Since QUIC uses Connection IDs, it can demultiplex a listener (accepting incoming connections) and an arbitrary number of outgoing QUIC connections on the same UDP socket.
|
||||
|
||||
```go
|
||||
udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 1234})
|
||||
// ... error handling
|
||||
tr := quic.Transport{
|
||||
Conn: udpConn,
|
||||
}
|
||||
ln, err := tr.Listen(tlsConf, quicConf)
|
||||
// ... error handling
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
// ... error handling
|
||||
// handle the connection, usually in a new Go routine
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The listener `ln` can now be used to accept incoming QUIC connections by (repeatedly) calling the `Accept` method (see below for more information on the `quic.Connection`).
|
||||
|
||||
As a shortcut, `quic.Listen` and `quic.ListenAddr` can be used without explicitly initializing a `quic.Transport`:
|
||||
|
||||
```
|
||||
ln, err := quic.Listen(udpConn, tlsConf, quicConf)
|
||||
```
|
||||
|
||||
When using the shortcut, it's not possible to reuse the same UDP socket for outgoing connections.
|
||||
|
||||
### Running a Client
|
||||
|
||||
As mentioned above, multiple outgoing connections can share a single UDP socket, since QUIC uses Connection IDs to demultiplex connections.
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
|
||||
defer cancel()
|
||||
conn, err := tr.Dial(ctx, <server address>, <tls.Config>, <quic.Config>)
|
||||
// ... error handling
|
||||
```
|
||||
|
||||
As a shortcut, `quic.Dial` and `quic.DialAddr` can be used without explictly initializing a `quic.Transport`:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
|
||||
defer cancel()
|
||||
conn, err := quic.Dial(ctx, conn, <server address>, <tls.Config>, <quic.Config>)
|
||||
```
|
||||
|
||||
Just as we saw before when used a similar shortcut to run a server, it's also not possible to reuse the same UDP socket for other outgoing connections, or to listen for incoming connections.
|
||||
|
||||
### Using a QUIC Connection
|
||||
|
||||
#### Accepting Streams
|
||||
|
||||
QUIC is a stream-multiplexed transport. A `quic.Connection` fundamentally differs from the `net.Conn` and the `net.PacketConn` interface defined in the standard library. Data is sent and received on (unidirectional and bidirectional) streams (and, if supported, in [datagrams](#quic-datagrams)), not on the connection itself. The stream state machine is described in detail in [Section 3 of RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000#section-3).
|
||||
|
||||
Note: A unidirectional stream is a stream that the initiator can only write to (`quic.SendStream`), and the receiver can only read from (`quic.ReceiveStream`). A bidirectional stream (`quic.Stream`) allows reading from and writing to for both sides.
|
||||
|
||||
On the receiver side, streams are accepted using the `AcceptStream` (for bidirectional) and `AcceptUniStream` functions. For most user cases, it makes sense to call these functions in a loop:
|
||||
|
||||
```go
|
||||
for {
|
||||
str, err := conn.AcceptStream(context.Background()) // for bidirectional streams
|
||||
// ... error handling
|
||||
// handle the stream, usually in a new Go routine
|
||||
}
|
||||
```
|
||||
|
||||
These functions return an error when the underlying QUIC connection is closed.
|
||||
|
||||
#### Opening Streams
|
||||
|
||||
There are two slightly different ways to open streams, one synchronous and one (potentially) asynchronous. This API is necessary since the receiver grants us a certain number of streams that we're allowed to open. It may grant us additional streams later on (typically when existing streams are closed), but it means that at the time we want to open a new stream, we might not be able to do so.
|
||||
|
||||
Using the synchronous method `OpenStreamSync` for bidirectional streams, and `OpenUniStreamSync` for unidirectional streams, an application can block until the peer allows opening additional streams. In case that we're allowed to open a new stream, these methods return right away:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
str, err := conn.OpenStreamSync(ctx) // wait up to 5s to open a new bidirectional stream
|
||||
```
|
||||
|
||||
The asynchronous version never blocks. If it's currently not possible to open a new stream, it returns a `net.Error` timeout error:
|
||||
|
||||
```go
|
||||
str, err := conn.OpenStream()
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
// It's currently not possible to open another stream,
|
||||
// but it might be possible later, once the peer allowed us to do so.
|
||||
}
|
||||
```
|
||||
|
||||
These functions return an error when the underlying QUIC connection is closed.
|
||||
|
||||
#### Using Streams
|
||||
|
||||
Using QUIC streams is pretty straightforward. The `quic.ReceiveStream` implements the `io.Reader` interface, and the `quic.SendStream` implements the `io.Writer` interface. A bidirectional stream (`quic.Stream`) implements both these interfaces. Conceptually, a bidirectional stream can be thought of as the composition of two unidirectional streams in opposite directions.
|
||||
|
||||
Calling `Close` on a `quic.SendStream` or a `quic.Stream` closes the send side of the stream. On the receiver side, this will be surfaced as an `io.EOF` returned from the `io.Reader` once all data has been consumed. Note that for bidirectional streams, `Close` _only_ closes the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.
|
||||
|
||||
In case the application wishes to abort sending on a `quic.SendStream` or a `quic.Stream` , it can reset the send side by calling `CancelWrite` with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a `quic.StreamError` containing that error code on the `io.Reader`. Note that for bidirectional streams, `CancelWrite` _only_ resets the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.
|
||||
|
||||
Conversely, in case the application wishes to abort receiving from a `quic.ReceiveStream` or a `quic.Stream`, it can ask the sender to abort data transmission by calling `CancelRead` with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a `quic.StreamError` containing that error code on the `io.Writer`. Note that for bidirectional streams, `CancelWrite` _only_ resets the receive side of the stream. It is still possible to write to the stream.
|
||||
|
||||
A bidirectional stream is only closed once both the read and the write side of the stream have been either closed and reset. Only then the peer is granted a new stream according to the maximum number of concurrent streams configured via `quic.Config.MaxIncomingStreams`.
|
||||
|
||||
### Configuring QUIC
|
||||
|
||||
The `quic.Config` struct passed to both the listen and dial calls (see above) contains a wide range of configuration options for QUIC connections, incl. the ability to fine-tune flow control limits, the number of streams that the peer is allowed to open concurrently, keep-alives, idle timeouts, and many more. Please refer to the documentation for the `quic.Config` for details.
|
||||
|
||||
The `quic.Transport` contains a few configuration options that don't apply to any single QUIC connection, but to all connections handled by that transport. It is highly recommend to set the `StatelessResetToken`, which allows endpoints to quickly recover from crashes / reboots of our node (see [Section 10.3 of RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000#section-10.3)).
|
||||
|
||||
### Closing a Connection
|
||||
|
||||
#### When the remote Peer closes the Connection
|
||||
|
||||
In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Additionally, it is set as cancellation cause of the connection context. Users can use errors assertions to find out what exactly went wrong:
|
||||
|
||||
* `quic.VersionNegotiationError`: Happens during the handshake, if there is no overlap between our and the remote's supported QUIC versions.
|
||||
* `quic.HandshakeTimeoutError`: Happens if the QUIC handshake doesn't complete within the time specified in `quic.Config.HandshakeTimeout`.
|
||||
* `quic.IdleTimeoutError`: Happens after completion of the handshake if the connection is idle for longer than the minimum of both peers idle timeouts (as configured by `quic.Config.IdleTimeout`). The connection is considered idle when no stream data (and datagrams, if applicable) are exchanged for that period. The QUIC connection can be instructed to regularly send a packet to prevent a connection from going idle by setting `quic.Config.KeepAlive`. However, this is no guarantee that the peer doesn't suddenly go away (e.g. by abruptly shutting down the node or by crashing), or by a NAT binding expiring, in which case this error might still occur.
|
||||
* `quic.StatelessResetError`: Happens when the remote peer lost the state required to decrypt the packet. This requires the `quic.Transport.StatelessResetToken` to be configured by the peer.
|
||||
* `quic.TransportError`: Happens if when the QUIC protocol is violated. Unless the error code is `APPLICATION_ERROR`, this will not happen unless one of the QUIC stacks involved is misbehaving. Please open an issue if you encounter this error.
|
||||
* `quic.ApplicationError`: Happens when the remote decides to close the connection, see below.
|
||||
|
||||
#### Initiated by the Application
|
||||
|
||||
A `quic.Connection` can be closed using `CloseWithError`:
|
||||
|
||||
```go
|
||||
conn.CloseWithError(0x42, "error 0x42 occurred")
|
||||
```
|
||||
|
||||
Applications can transmit both an error code (an unsigned 62-bit number) as well as a UTF-8 encoded human-readable reason. The error code allows the receiver to learn why the connection was closed, and the reason can be useful for debugging purposes.
|
||||
|
||||
On the receiver side, this is surfaced as a `quic.ApplicationError`.
|
||||
|
||||
### QUIC Datagrams
|
||||
|
||||
Unreliable datagrams are a QUIC extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221)) that is negotiated during the handshake. Support can be enabled by setting the `quic.Config.EnableDatagram` flag. Note that this doesn't guarantee that the peer also supports datagrams. Whether or not the feature negotiation succeeded can be learned from the `quic.ConnectionState.SupportsDatagrams` obtained from `quic.Connection.ConnectionState()`.
|
||||
|
||||
QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not automatically retransmitted.
|
||||
|
||||
Datagrams are sent using the `SendMessage` method on the `quic.Connection`:
|
||||
|
||||
```go
|
||||
conn.SendMessage([]byte("foobar"))
|
||||
```
|
||||
|
||||
And received using `ReceiveMessage`:
|
||||
|
||||
```go
|
||||
msg, err := conn.ReceiveMessage()
|
||||
```
|
||||
|
||||
Note that this code path is currently not optimized. It works for datagrams that are sent occasionally, but it doesn't achieve the same throughput as writing data on a stream. Please get in touch on issue #3766 if your use case relies on high datagram throughput, or if you'd like to help fix this issue. There are also some restrictions regarding the maximum message size (see #3599).
|
||||
|
||||
|
||||
|
||||
## Using HTTP/3
|
||||
|
||||
### As a server
|
||||
|
||||
See the [example server](example/main.go). Starting a QUIC server is very similar to the standard library http package in Go:
|
||||
|
||||
```go
|
||||
http.Handle("/", http.FileServer(http.Dir(wwwDir)))
|
||||
http3.ListenAndServeQUIC("localhost:4242", "/path/to/cert/chain.pem", "/path/to/privkey.pem", nil)
|
||||
```
|
||||
|
||||
### As a client
|
||||
|
||||
See the [example client](example/client/main.go). Use a `http3.RoundTripper` as a `Transport` in a `http.Client`.
|
||||
|
||||
```go
|
||||
http.Client{
|
||||
Transport: &http3.RoundTripper{},
|
||||
}
|
||||
```
|
||||
Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/).
|
||||
|
||||
## Projects using quic-go
|
||||
|
||||
| Project | Description | Stars |
|
||||
|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) | Free and open source, powerful network-wide ads & trackers blocking DNS server. | ![GitHub Repo stars](https://img.shields.io/github/stars/AdguardTeam/AdGuardHome?style=flat-square) |
|
||||
| [algernon](https://github.com/xyproto/algernon) | Small self-contained pure-Go web server with Lua, Markdown, HTTP/2, QUIC, Redis and PostgreSQL support | ![GitHub Repo stars](https://img.shields.io/github/stars/xyproto/algernon?style=flat-square) |
|
||||
| [caddy](https://github.com/caddyserver/caddy/) | Fast, multi-platform web server with automatic HTTPS | ![GitHub Repo stars](https://img.shields.io/github/stars/caddyserver/caddy?style=flat-square) |
|
||||
| [cloudflared](https://github.com/cloudflare/cloudflared) | A tunneling daemon that proxies traffic from the Cloudflare network to your origins | ![GitHub Repo stars](https://img.shields.io/github/stars/cloudflare/cloudflared?style=flat-square) |
|
||||
| [go-libp2p](https://github.com/libp2p/go-libp2p) | libp2p implementation in Go, powering [Kubo](https://github.com/ipfs/kubo) (IPFS) and [Lotus](https://github.com/filecoin-project/lotus) (Filecoin), among others | ![GitHub Repo stars](https://img.shields.io/github/stars/libp2p/go-libp2p?style=flat-square) |
|
||||
| [frp](https://github.com/fatedier/frp) | A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet | ![GitHub Repo stars](https://img.shields.io/github/stars/fatedier/frp?style=flat-square) |
|
||||
| [go-libp2p](https://github.com/libp2p/go-libp2p) | libp2p implementation in Go, powering [Kubo](https://github.com/ipfs/kubo) (IPFS) and [Lotus](https://github.com/filecoin-project/lotus) (Filecoin), among others | ![GitHub Repo stars](https://img.shields.io/github/stars/libp2p/go-libp2p?style=flat-square) |
|
||||
| [gost](https://github.com/go-gost/gost) | A simple security tunnel written in Go | ![GitHub Repo stars](https://img.shields.io/github/stars/go-gost/gost?style=flat-square) |
|
||||
| [Hysteria](https://github.com/apernet/hysteria) | A powerful, lightning fast and censorship resistant proxy | ![GitHub Repo stars](https://img.shields.io/github/stars/apernet/hysteria?style=flat-square) |
|
||||
| [Mercure](https://github.com/dunglas/mercure) | An open, easy, fast, reliable and battery-efficient solution for real-time communications | ![GitHub Repo stars](https://img.shields.io/github/stars/dunglas/mercure?style=flat-square) |
|
||||
| [OONI Probe](https://github.com/ooni/probe-cli) | Next generation OONI Probe. Library and CLI tool. | ![GitHub Repo stars](https://img.shields.io/github/stars/ooni/probe-cli?style=flat-square) |
|
||||
| [RoadRunner](https://github.com/roadrunner-server/roadrunner) | High-performance PHP application server, process manager written in Go and powered with plugins | ![GitHub Repo stars](https://img.shields.io/github/stars/roadrunner-server/roadrunner?style=flat-square) |
|
||||
| [syncthing](https://github.com/syncthing/syncthing/) | Open Source Continuous File Synchronization | ![GitHub Repo stars](https://img.shields.io/github/stars/syncthing/syncthing?style=flat-square) |
|
||||
| [traefik](https://github.com/traefik/traefik) | The Cloud Native Application Proxy | ![GitHub Repo stars](https://img.shields.io/github/stars/traefik/traefik?style=flat-square) |
|
||||
| [v2ray-core](https://github.com/v2fly/v2ray-core) | A platform for building proxies to bypass network restrictions | ![GitHub Repo stars](https://img.shields.io/github/stars/v2fly/v2ray-core?style=flat-square) |
|
||||
|
@ -218,11 +45,6 @@ If you'd like to see your project added to this list, please send us a PR.
|
|||
|
||||
quic-go always aims to support the latest two Go releases.
|
||||
|
||||
### Dependency on forked crypto/tls
|
||||
|
||||
Since the standard library didn't provide any QUIC APIs before the Go 1.21 release, we had to fork crypto/tls to add the required APIs ourselves: [qtls for Go 1.20](https://github.com/quic-go/qtls-go1-20).
|
||||
This had led to a lot of pain in the Go ecosystem, and we're happy that we can rely on Go 1.21 going forward.
|
||||
|
||||
## Contributing
|
||||
|
||||
We are always happy to welcome new contributors! We have a number of self-contained issues that are suitable for first-time contributors, they are tagged with [help wanted](https://github.com/quic-go/quic-go/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). If you have any questions, please feel free to reach out by opening an issue or leaving a comment.
|
||||
|
|
|
@ -28,14 +28,14 @@ type client struct {
|
|||
|
||||
initialPacketNumber protocol.PacketNumber
|
||||
hasNegotiatedVersion bool
|
||||
version protocol.VersionNumber
|
||||
version protocol.Version
|
||||
|
||||
handshakeChan chan struct{}
|
||||
|
||||
conn quicConn
|
||||
|
||||
tracer logging.ConnectionTracer
|
||||
tracingID uint64
|
||||
tracer *logging.ConnectionTracer
|
||||
tracingID ConnectionTracingID
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
|
@ -55,11 +55,11 @@ func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Confi
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dl, err := setupTransport(udpConn, tlsConf, true)
|
||||
tr, err := setupTransport(udpConn, tlsConf, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dl.Dial(ctx, udpAddr, tlsConf, conf)
|
||||
return tr.dial(ctx, udpAddr, addr, tlsConf, conf, false)
|
||||
}
|
||||
|
||||
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
|
||||
|
@ -73,13 +73,13 @@ func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dl, err := setupTransport(udpConn, tlsConf, true)
|
||||
tr, err := setupTransport(udpConn, tlsConf, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := dl.DialEarly(ctx, udpAddr, tlsConf, conf)
|
||||
conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, true)
|
||||
if err != nil {
|
||||
dl.Close()
|
||||
tr.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
|
@ -153,7 +153,7 @@ func dial(
|
|||
if c.config.Tracer != nil {
|
||||
c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
|
||||
}
|
||||
if c.tracer != nil {
|
||||
if c.tracer != nil && c.tracer.StartedConnection != nil {
|
||||
c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
|
||||
}
|
||||
if err := c.dial(ctx); err != nil {
|
||||
|
@ -163,12 +163,6 @@ func dial(
|
|||
}
|
||||
|
||||
func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
|
||||
if tlsConf == nil {
|
||||
tlsConf = &tls.Config{}
|
||||
} else {
|
||||
tlsConf = tlsConf.Clone()
|
||||
}
|
||||
|
||||
srcConnID, err := connIDGenerator.GenerateConnectionID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -238,8 +232,8 @@ func (c *client) dial(ctx context.Context) error {
|
|||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.conn.shutdown()
|
||||
return ctx.Err()
|
||||
c.conn.destroy(nil)
|
||||
return context.Cause(ctx)
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
case recreateErr := <-recreateChan:
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"math/bits"
|
||||
"net"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
|
||||
|
@ -12,9 +11,8 @@ import (
|
|||
// When receiving packets for such a connection, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
|
||||
// with an exponential backoff.
|
||||
type closedLocalConn struct {
|
||||
counter uint32
|
||||
perspective protocol.Perspective
|
||||
logger utils.Logger
|
||||
counter uint32
|
||||
logger utils.Logger
|
||||
|
||||
sendPacket func(net.Addr, packetInfo)
|
||||
}
|
||||
|
@ -22,11 +20,10 @@ type closedLocalConn struct {
|
|||
var _ packetHandler = &closedLocalConn{}
|
||||
|
||||
// newClosedLocalConn creates a new closedLocalConn and runs it.
|
||||
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), pers protocol.Perspective, logger utils.Logger) packetHandler {
|
||||
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), logger utils.Logger) packetHandler {
|
||||
return &closedLocalConn{
|
||||
sendPacket: sendPacket,
|
||||
perspective: pers,
|
||||
logger: logger,
|
||||
sendPacket: sendPacket,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,24 +38,20 @@ func (c *closedLocalConn) handlePacket(p receivedPacket) {
|
|||
c.sendPacket(p.remoteAddr, p.info)
|
||||
}
|
||||
|
||||
func (c *closedLocalConn) shutdown() {}
|
||||
func (c *closedLocalConn) destroy(error) {}
|
||||
func (c *closedLocalConn) getPerspective() protocol.Perspective { return c.perspective }
|
||||
func (c *closedLocalConn) destroy(error) {}
|
||||
func (c *closedLocalConn) closeWithTransportError(TransportErrorCode) {}
|
||||
|
||||
// A closedRemoteConn is a connection that was closed remotely.
|
||||
// For such a connection, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
|
||||
// We can just ignore those packets.
|
||||
type closedRemoteConn struct {
|
||||
perspective protocol.Perspective
|
||||
}
|
||||
type closedRemoteConn struct{}
|
||||
|
||||
var _ packetHandler = &closedRemoteConn{}
|
||||
|
||||
func newClosedRemoteConn(pers protocol.Perspective) packetHandler {
|
||||
return &closedRemoteConn{perspective: pers}
|
||||
func newClosedRemoteConn() packetHandler {
|
||||
return &closedRemoteConn{}
|
||||
}
|
||||
|
||||
func (s *closedRemoteConn) handlePacket(receivedPacket) {}
|
||||
func (s *closedRemoteConn) shutdown() {}
|
||||
func (s *closedRemoteConn) destroy(error) {}
|
||||
func (s *closedRemoteConn) getPerspective() protocol.Perspective { return s.perspective }
|
||||
func (c *closedRemoteConn) handlePacket(receivedPacket) {}
|
||||
func (c *closedRemoteConn) destroy(error) {}
|
||||
func (c *closedRemoteConn) closeWithTransportError(TransportErrorCode) {}
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
coverage:
|
||||
round: nearest
|
||||
ignore:
|
||||
- streams_map_incoming_bidi.go
|
||||
- streams_map_incoming_uni.go
|
||||
- streams_map_outgoing_bidi.go
|
||||
- streams_map_outgoing_uni.go
|
||||
- http3/gzip_reader.go
|
||||
- interop/
|
||||
- internal/ackhandler/packet_linkedlist.go
|
||||
- internal/handshake/cipher_suite.go
|
||||
- internal/utils/byteinterval_linkedlist.go
|
||||
- internal/utils/newconnectionid_linkedlist.go
|
||||
- internal/utils/packetinterval_linkedlist.go
|
||||
- internal/utils/linkedlist/linkedlist.go
|
||||
- logging/null_tracer.go
|
||||
- internal/testdata
|
||||
- testutils/
|
||||
- fuzzing/
|
||||
- metrics/
|
||||
status:
|
||||
|
|
|
@ -2,11 +2,9 @@ package quic
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
)
|
||||
|
||||
|
@ -17,7 +15,11 @@ func (c *Config) Clone() *Config {
|
|||
}
|
||||
|
||||
func (c *Config) handshakeTimeout() time.Duration {
|
||||
return utils.Max(protocol.DefaultHandshakeTimeout, 2*c.HandshakeIdleTimeout)
|
||||
return 2 * c.HandshakeIdleTimeout
|
||||
}
|
||||
|
||||
func (c *Config) maxRetryTokenAge() time.Duration {
|
||||
return c.handshakeTimeout()
|
||||
}
|
||||
|
||||
func validateConfig(config *Config) error {
|
||||
|
@ -46,22 +48,6 @@ func validateConfig(config *Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// populateServerConfig populates fields in the quic.Config with their default values, if none are set
|
||||
// it may be called with nil
|
||||
func populateServerConfig(config *Config) *Config {
|
||||
config = populateConfig(config)
|
||||
if config.MaxTokenAge == 0 {
|
||||
config.MaxTokenAge = protocol.TokenValidity
|
||||
}
|
||||
if config.MaxRetryTokenAge == 0 {
|
||||
config.MaxRetryTokenAge = protocol.RetryTokenValidity
|
||||
}
|
||||
if config.RequireAddressValidation == nil {
|
||||
config.RequireAddressValidation = func(net.Addr) bool { return false }
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// populateConfig populates fields in the quic.Config with their default values, if none are set
|
||||
// it may be called with nil
|
||||
func populateConfig(config *Config) *Config {
|
||||
|
@ -110,26 +96,22 @@ func populateConfig(config *Config) *Config {
|
|||
}
|
||||
|
||||
return &Config{
|
||||
GetConfigForClient: config.GetConfigForClient,
|
||||
Versions: versions,
|
||||
HandshakeIdleTimeout: handshakeIdleTimeout,
|
||||
MaxIdleTimeout: idleTimeout,
|
||||
MaxTokenAge: config.MaxTokenAge,
|
||||
MaxRetryTokenAge: config.MaxRetryTokenAge,
|
||||
RequireAddressValidation: config.RequireAddressValidation,
|
||||
KeepAlivePeriod: config.KeepAlivePeriod,
|
||||
InitialStreamReceiveWindow: initialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: maxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
|
||||
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
|
||||
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
|
||||
MaxIncomingStreams: maxIncomingStreams,
|
||||
MaxIncomingUniStreams: maxIncomingUniStreams,
|
||||
TokenStore: config.TokenStore,
|
||||
EnableDatagrams: config.EnableDatagrams,
|
||||
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
|
||||
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
|
||||
Allow0RTT: config.Allow0RTT,
|
||||
Tracer: config.Tracer,
|
||||
GetConfigForClient: config.GetConfigForClient,
|
||||
Versions: versions,
|
||||
HandshakeIdleTimeout: handshakeIdleTimeout,
|
||||
MaxIdleTimeout: idleTimeout,
|
||||
KeepAlivePeriod: config.KeepAlivePeriod,
|
||||
InitialStreamReceiveWindow: initialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: maxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
|
||||
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
|
||||
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
|
||||
MaxIncomingStreams: maxIncomingStreams,
|
||||
MaxIncomingUniStreams: maxIncomingUniStreams,
|
||||
TokenStore: config.TokenStore,
|
||||
EnableDatagrams: config.EnableDatagrams,
|
||||
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
|
||||
Allow0RTT: config.Allow0RTT,
|
||||
Tracer: config.Tracer,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
|
||||
|
@ -20,7 +19,7 @@ type connIDGenerator struct {
|
|||
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken
|
||||
removeConnectionID func(protocol.ConnectionID)
|
||||
retireConnectionID func(protocol.ConnectionID)
|
||||
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte)
|
||||
replaceWithClosed func([]protocol.ConnectionID, []byte)
|
||||
queueControlFrame func(wire.Frame)
|
||||
}
|
||||
|
||||
|
@ -31,7 +30,7 @@ func newConnIDGenerator(
|
|||
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken,
|
||||
removeConnectionID func(protocol.ConnectionID),
|
||||
retireConnectionID func(protocol.ConnectionID),
|
||||
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte),
|
||||
replaceWithClosed func([]protocol.ConnectionID, []byte),
|
||||
queueControlFrame func(wire.Frame),
|
||||
generator ConnectionIDGenerator,
|
||||
) *connIDGenerator {
|
||||
|
@ -60,7 +59,7 @@ func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
|
|||
// transport parameter.
|
||||
// We currently don't send the preferred_address transport parameter,
|
||||
// so we can issue (limit - 1) connection IDs.
|
||||
for i := uint64(len(m.activeSrcConnIDs)); i < utils.Min(limit, protocol.MaxIssuedConnectionIDs); i++ {
|
||||
for i := uint64(len(m.activeSrcConnIDs)); i < min(limit, protocol.MaxIssuedConnectionIDs); i++ {
|
||||
if err := m.issueNewConnID(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -127,7 +126,7 @@ func (m *connIDGenerator) RemoveAll() {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose []byte) {
|
||||
func (m *connIDGenerator) ReplaceWithClosed(connClose []byte) {
|
||||
connIDs := make([]protocol.ConnectionID, 0, len(m.activeSrcConnIDs)+1)
|
||||
if m.initialClientDestConnID != nil {
|
||||
connIDs = append(connIDs, *m.initialClientDestConnID)
|
||||
|
@ -135,5 +134,5 @@ func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose
|
|||
for _, connID := range m.activeSrcConnIDs {
|
||||
connIDs = append(connIDs, connID)
|
||||
}
|
||||
m.replaceWithClosed(connIDs, pers, connClose)
|
||||
m.replaceWithClosed(connIDs, connClose)
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ func (h *connIDManager) updateConnectionID() {
|
|||
h.queueControlFrame(&wire.RetireConnectionIDFrame{
|
||||
SequenceNumber: h.activeSequenceNumber,
|
||||
})
|
||||
h.highestRetired = utils.Max(h.highestRetired, h.activeSequenceNumber)
|
||||
h.highestRetired = max(h.highestRetired, h.activeSequenceNumber)
|
||||
if h.activeStatelessResetToken != nil {
|
||||
h.removeStatelessResetToken(*h.activeStatelessResetToken)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue