diff --git a/.github/Dockerfile b/.github/Dockerfile new file mode 100644 index 00000000..872f7e00 --- /dev/null +++ b/.github/Dockerfile @@ -0,0 +1,16 @@ +# build stage +FROM golang:alpine AS build +RUN apk update && apk add git +ADD . /src +WORKDIR /src +ENV CGO_ENABLED=0 +RUN go build \ + -ldflags "-X github.com/jpillora/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ + -o /tmp/bin +# run stage +FROM scratch +LABEL maintainer="dev@jpillora.com" +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +WORKDIR /app +COPY --from=build /tmp/bin /app/bin +ENTRYPOINT ["/app/bin"] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2c7d1708..2dd92b7b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,10 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "monthly" + +# Dependencies listed in go.mod + - package-ecosystem: "gomod" + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/gocompare.sh b/.github/gocompare.sh deleted file mode 100755 index e3496950..00000000 --- a/.github/gocompare.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# TODO -gocompat compare \ - --go1compat \ - --log-level=debug \ - --git-refs=origin/master..$(git rev-parse --abbrev-ref HEAD) \ - ./... diff --git a/.github/goreleaser.yml b/.github/goreleaser.yml index de19131c..2e447cb6 100644 --- a/.github/goreleaser.yml +++ b/.github/goreleaser.yml @@ -1,6 +1,6 @@ -# test this goreleaser config with: -# - cd chisel -# - goreleaser --skip-publish --rm-dist --config .github/goreleaser.yml +# test this file with +# goreleaser release --config goreleaser.yml --clean --snapshot +version: 2 builds: - id: chisel binary: chisel @@ -13,8 +13,25 @@ builds: goos: - linux - windows + - openbsd goarch: - amd64 + - arm + - arm64 + - ppc64 + - ppc64le + - mips + - mipsle + - mips64 + - mips64le + - s390x + goarm: + - 5 + - 6 + - 7 + gomips: + - hardfloat + - softfloat - id: chisel-garble binary: chisel-garble goos: @@ -24,6 +41,12 @@ builds: ldflags: - "" gobinary: "garble-literals" +nfpms: + - maintainer: "https://github.com/{{ .Env.GITHUB_USER }}" + formats: + - deb + - rpm + - apk archives: - id: chisel builds: [chisel] @@ -38,6 +61,7 @@ archives: files: - none* release: + draft: true prerelease: auto changelog: sort: asc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de78710f..e7e7f6f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,35 +1,41 @@ -on: [push, pull_request] name: CI +on: + pull_request: {} + push: {} +permissions: write-all jobs: # ================ - # TEST JOB - # runs on every push and PR - # runs 2x3 times (see matrix) + # BUILD AND TEST JOB # ================ test: - name: Test + name: Build & Test strategy: matrix: - go-version: [1.19.x] - platform: [ubuntu-latest, windows-latest] + # optionally test/build across multiple platforms/Go-versions + go-version: ["stable"] # '1.16', '1.17', '1.18, + platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - - name: Install Go + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v3 + check-latest: true - name: Install garble - run: go install -v mvdan.cc/garble@v0.7.2 + run: go install -v mvdan.cc/garble@latest - name: Build run: garble -literals -tiny build -v ${{ github.workspace }} - name: Test run: go test -v ./... - env: - GODEBUG: x509ignoreCN=0 - - goreleaser: + # ================ + # RELEASE BINARIES (on push "v*" tag) + # ================ + release_binaries: + name: Release Binaries needs: test if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest @@ -43,11 +49,11 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: latest - name: Install garble run: | - go install mvdan.cc/garble@v0.7.2 + go install mvdan.cc/garble@latest sudo cp garble-literals.sh /usr/bin/garble-literals sudo chmod +x /usr/bin/garble-literals - @@ -62,3 +68,44 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + + # ================ + # RELEASE DOCKER IMAGES (on push "v*" tag) + # ================ + release_docker: + name: Release Docker Images + needs: test + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: jpillora + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: jpillora/chisel + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: .github/Dockerfile + platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/386,linux/arm/v7,linux/arm/v6 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e3e2073e..00000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# build stage -FROM golang:alpine AS build-env -LABEL maintainer="dev@jpillora.com" -RUN apk update -RUN apk add git -ENV CGO_ENABLED 0 -ADD . /src -WORKDIR /src -RUN go build \ - -ldflags "-X github.com/jpillora/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ - -o chisel -# container stage -FROM alpine -RUN apk update && apk add --no-cache ca-certificates -WORKDIR /app -COPY --from=build-env /src/chisel /app/chisel -ENTRYPOINT ["/app/chisel"] diff --git a/LICENSE b/LICENSE index 7ae236f7..08d56bd7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Jaime Pillora +Copyright (c) 2024 Jaime Pillora Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ad3e39fd..6fa54cfe 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,23 @@ $ chisel server --help --port, -p, Defines the HTTP listening port (defaults to the environment variable PORT and fallsback to port 8080). - --key, An optional string to seed the generation of a ECDSA public + --key, (deprecated use --keygen and --keyfile instead) + An optional string to seed the generation of a ECDSA public and private key pair. All communications will be secured using this key pair. Share the subsequent fingerprint with clients to enable detection of man-in-the-middle attacks (defaults to the CHISEL_KEY environment variable, otherwise a new key is generate each run). + --keygen, A path to write a newly generated PEM-encoded SSH private key file. + If users depend on your --key fingerprint, you may also include your --key to + output your existing key. Use - (dash) to output the generated key to stdout. + + --keyfile, An optional path to a PEM-encoded SSH private key. When + this flag is set, the --key option is ignored, and the provided private key + is used to secure all communications. (defaults to the CHISEL_KEY_FILE + environment variable). Since ECDSA keys are short, you may also set keyfile + to an inline base64 private key (e.g. chisel server --keygen - | base64). + --authfile, An optional path to a users.json file. This file should be an object with users defined like: { @@ -167,7 +178,7 @@ $ chisel server --help and you cannot set --tls-domain. --tls-domain, Enables TLS and automatically acquires a TLS key and - certificate using LetsEncypt. Setting --tls-domain requires port 443. + certificate using LetsEncrypt. Setting --tls-domain requires port 443. You may specify multiple --tls-domain flags to serve multiple domains. The resulting files are cached in the "$HOME/.cache/chisel" directory. You can modify this path by setting the CHISEL_LE_CACHE variable, @@ -304,6 +315,9 @@ $ chisel client --help --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). + --sni, Override the ServerName when using TLS (defaults to the + hostname). + --tls-ca, An optional root certificate bundle used to verify the chisel server. Only valid when connecting to the server with "https" or "wss". By default, the operating system CAs will be used. @@ -345,7 +359,7 @@ $ chisel client --help ### Security -Encryption is always enabled. When you start up a chisel server, it will generate an in-memory ECDSA public/private key pair. The public key fingerprint (base64 encoded SHA256) will be displayed as the server starts. Instead of generating a random key, the server may optionally specify a key seed, using the `--key` option, which will be used to seed the key generation. When clients connect, they will also display the server's public key fingerprint. The client can force a particular fingerprint using the `--fingerprint` option. See the `--help` above for more information. +Encryption is always enabled. When you start up a chisel server, it will generate an in-memory ECDSA public/private key pair. The public key fingerprint (base64 encoded SHA256) will be displayed as the server starts. Instead of generating a random key, the server may optionally specify a key file, using the `--keyfile` option. When clients connect, they will also display the server's public key fingerprint. The client can force a particular fingerprint using the `--fingerprint` option. See the `--help` above for more information. ### Authentication @@ -353,30 +367,34 @@ Using the `--authfile` option, the server may optionally provide a `user.json` c Internally, this is done using the _Password_ authentication method provided by SSH. Learn more about `crypto/ssh` here http://blog.gopheracademy.com/go-and-ssh/. -### SOCKS5 Guide +### SOCKS5 Guide with Docker + +1. Print a new private key to the terminal + + ```sh + chisel server --keygen - + # or save it to disk --keygen /path/to/mykey + ``` 1. Start your chisel server -```sh -docker run \ - --name chisel -p 9312:9312 \ - -d --restart always \ - jpillora/chisel server -p 9312 --socks5 --key supersecret -``` + ```sh + jpillora/chisel server --keyfile '' -p 9312 --socks5 + ``` -2. Connect your chisel client (using server's fingerprint) +1. Connect your chisel client (using server's fingerprint) -```sh -chisel client --fingerprint 'rHb55mcxf6vSckL2AezFV09rLs7pfPpavVu++MF7AhQ=' :9312 socks -``` + ```sh + chisel client --fingerprint '' :9312 socks + ``` -3. Point your SOCKS5 clients (e.g. OS/Browser) to: +1. Point your SOCKS5 clients (e.g. OS/Browser) to: -``` -:1080 -``` + ``` + :1080 + ``` -4. Now you have an encrypted, authenticated SOCKS5 connection over HTTP +1. Now you have an encrypted, authenticated SOCKS5 connection over HTTP #### Caveats @@ -407,6 +425,9 @@ Since WebSockets support is required: - `1.5` - Added reverse SOCKS support (by @aus) - `1.6` - Added client stdio support (by @BoleynSu) - `1.7` - Added UDP support +- `1.8` - Move to a `scratch`Docker image +- `1.9` - Bump to Go 1.21. Switch from `--key` seed to P256 key strings with `--key{gen,file}` (by @cmenginnz) +- `1.10` - Bump to Go 1.22. Add `.rpm` `.deb` and `.akp` to releases. Fix bad version comparison. ## License diff --git a/client/client.go b/client/client.go index 9cdd5bb9..79a1cc19 100644 --- a/client/client.go +++ b/client/client.go @@ -8,7 +8,6 @@ import ( "encoding/base64" "errors" "fmt" - "io/ioutil" "log" "net" "net/http" @@ -44,6 +43,7 @@ type Config struct { Headers http.Header TLS TLSConfig DialContext func(ctx context.Context, network, addr string) (net.Conn, error) + Verbose bool } // TLSConfig for a Client @@ -52,6 +52,7 @@ type TLSConfig struct { CA string Cert string Key string + ServerName string } // Client represents a client instance @@ -112,13 +113,16 @@ func NewClient(c *Config) (*Client, error) { //configure tls if u.Scheme == "wss" { tc := &tls.Config{} + if c.TLS.ServerName != "" { + tc.ServerName = c.TLS.ServerName + } //certificate verification config if c.TLS.SkipVerify { client.Infof("TLS verification disabled") tc.InsecureSkipVerify = true } else if c.TLS.CA != "" { rootCAs := x509.NewCertPool() - if b, err := ioutil.ReadFile(c.TLS.CA); err != nil { + if b, err := os.ReadFile(c.TLS.CA); err != nil { return nil, fmt.Errorf("Failed to load file: %s", c.TLS.CA) } else if ok := rootCAs.AppendCertsFromPEM(b); !ok { return nil, fmt.Errorf("Failed to decode PEM: %s", c.TLS.CA) diff --git a/client/client_connect.go b/client/client_connect.go index 8841d0ca..884c7647 100644 --- a/client/client_connect.go +++ b/client/client_connect.go @@ -64,7 +64,7 @@ func (c *Client) connectionLoop(ctx context.Context) error { return nil } -//connectionOnce connects to the chisel server and blocks +// connectionOnce connects to the chisel server and blocks func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error) { //already closed? select { @@ -82,6 +82,7 @@ func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error) TLSClientConfig: c.tlsConfig, ReadBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0), WriteBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0), + NetDialContext: c.config.DialContext, } //optional proxy if p := c.proxyURL; p != nil { diff --git a/client/client_test.go b/client/client_test.go index 598293d1..66b4cc4e 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1,7 +1,6 @@ package chclient import ( - "crypto/ecdsa" "crypto/elliptic" "log" "net/http" @@ -55,7 +54,7 @@ func TestFallbackLegacyFingerprint(t *testing.T) { t.Fatal(err) } r := ccrypto.NewDetermRand([]byte("test123")) - priv, err := ecdsa.GenerateKey(elliptic.P256(), r) + priv, err := ccrypto.GenerateKeyGo119(elliptic.P256(), r) if err != nil { t.Fatal(err) } @@ -79,7 +78,7 @@ func TestVerifyLegacyFingerprint(t *testing.T) { t.Fatal(err) } r := ccrypto.NewDetermRand([]byte("test123")) - priv, err := ecdsa.GenerateKey(elliptic.P256(), r) + priv, err := ccrypto.GenerateKeyGo119(elliptic.P256(), r) if err != nil { t.Fatal(err) } @@ -104,7 +103,7 @@ func TestVerifyFingerprint(t *testing.T) { t.Fatal(err) } r := ccrypto.NewDetermRand([]byte("test123")) - priv, err := ecdsa.GenerateKey(elliptic.P256(), r) + priv, err := ccrypto.GenerateKeyGo119(elliptic.P256(), r) if err != nil { t.Fatal(err) } diff --git a/example/Flyfile b/example/Flyfile new file mode 100644 index 00000000..ff87edb2 --- /dev/null +++ b/example/Flyfile @@ -0,0 +1,2 @@ +FROM jpillora/chisel +ENTRYPOINT ["/app/bin", "server", "--port", "443", "--tls-domain", "chisel.jpillora.com"] \ No newline at end of file diff --git a/example/fly.toml b/example/fly.toml new file mode 100644 index 00000000..c9b12f7d --- /dev/null +++ b/example/fly.toml @@ -0,0 +1,13 @@ +app = "jp-chisel" +kill_signal = "SIGINT" +kill_timeout = 5 +processes = [] + +[build] + dockerfile = "Flyfile" + +[[services]] + internal_port = 443 + protocol = "tcp" + [[services.ports]] + port = "443" \ No newline at end of file diff --git a/go.mod b/go.mod index 50e5cf78..34f0abe2 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,23 @@ module github.com/jpillora/chisel -go 1.16 +go 1.21 require ( - github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/fsnotify/fsnotify v1.7.0 - github.com/gorilla/websocket v1.5.1 - github.com/jpillora/ansi v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 + github.com/gorilla/websocket v1.5.0 github.com/jpillora/backoff v1.0.0 github.com/jpillora/requestlog v1.0.0 github.com/jpillora/sizestr v1.0.0 - github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect - golang.org/x/crypto v0.15.0 - golang.org/x/net v0.18.0 + golang.org/x/crypto v0.16.0 + golang.org/x/net v0.14.0 golang.org/x/sync v0.5.0 - mvdan.cc/garble v0.7.2 // indirect +) + +require ( + github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect + github.com/jpillora/ansi v1.0.3 // indirect + github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e5da519d..01030da4 100644 --- a/go.sum +++ b/go.sum @@ -2,18 +2,10 @@ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/bluekeyes/go-gitdiff v0.7.1 h1:graP4ElLRshr8ecu0UtqfNTCHrtSyZd3DABQm/DWesQ= -github.com/bluekeyes/go-gitdiff v0.7.1/go.mod h1:QpfYYO1E0fTVHVZAZKiRjtSGY9823iCdvGXBcEzHGbM= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jpillora/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ= github.com/jpillora/ansi v1.0.3/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -35,79 +27,16 @@ github.com/rogpeppe/go-internal v1.10.1-0.20230523133328-2d7bba0e58db h1:/cdA6+L github.com/rogpeppe/go-internal v1.10.1-0.20230523133328-2d7bba0e58db/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= -golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -mvdan.cc/garble v0.7.2 h1:JqoG3A+Hi4gkzJS4OZYSeeZA4N1FlXbTS3nAi1J8uJE= -mvdan.cc/garble v0.7.2/go.mod h1:DsUxHuTlrYN8DzGiO/0F3jf9w7OCE196Dga8NSYKNAg= diff --git a/main.go b/main.go index bba3e747..01f9ca3b 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "net/http" "os" @@ -15,7 +14,9 @@ import ( chclient "github.com/jpillora/chisel/client" chserver "github.com/jpillora/chisel/server" chshare "github.com/jpillora/chisel/share" + "github.com/jpillora/chisel/share/ccrypto" "github.com/jpillora/chisel/share/cos" + "github.com/jpillora/chisel/share/settings" ) var help = ` @@ -87,7 +88,7 @@ var commonHelp = ` func generatePidFile() { pid := []byte(strconv.Itoa(os.Getpid())) - if err := ioutil.WriteFile("chisel.pid", pid, 0644); err != nil { + if err := os.WriteFile("chisel.pid", pid, 0644); err != nil { log.Fatal(err) } } @@ -103,12 +104,23 @@ var serverHelp = ` --port, -p, Defines the HTTP listening port (defaults to the environment variable PORT and fallsback to port 8080). - --key, An optional string to seed the generation of a ECDSA public + --key, (deprecated use --keygen and --keyfile instead) + An optional string to seed the generation of a ECDSA public and private key pair. All communications will be secured using this key pair. Share the subsequent fingerprint with clients to enable detection of man-in-the-middle attacks (defaults to the CHISEL_KEY environment variable, otherwise a new key is generate each run). + --keygen, A path to write a newly generated PEM-encoded SSH private key file. + If users depend on your --key fingerprint, you may also include your --key to + output your existing key. Use - (dash) to output the generated key to stdout. + + --keyfile, An optional path to a PEM-encoded SSH private key. When + this flag is set, the --key option is ignored, and the provided private key + is used to secure all communications. (defaults to the CHISEL_KEY_FILE + environment variable). Since ECDSA keys are short, you may also set keyfile + to an inline base64 private key (e.g. chisel server --keygen - | base64). + --authfile, An optional path to a users.json file. This file should be an object with users defined like: { @@ -151,7 +163,7 @@ var serverHelp = ` and you cannot set --tls-domain. --tls-domain, Enables TLS and automatically acquires a TLS key and - certificate using LetsEncypt. Setting --tls-domain requires port 443. + certificate using LetsEncrypt. Setting --tls-domain requires port 443. You may specify multiple --tls-domain flags to serve multiple domains. The resulting files are cached in the "$HOME/.cache/chisel" directory. You can modify this path by setting the CHISEL_LE_CACHE variable, @@ -170,6 +182,7 @@ func server(args []string) { config := &chserver.Config{} flags.StringVar(&config.KeySeed, "key", "", "") + flags.StringVar(&config.KeyFile, "keyfile", "", "") flags.StringVar(&config.AuthFile, "authfile", "", "") flags.StringVar(&config.Auth, "auth", "", "") flags.DurationVar(&config.KeepAlive, "keepalive", 25*time.Second, "") @@ -187,6 +200,7 @@ func server(args []string) { port := flags.String("port", "", "") pid := flags.Bool("pid", false, "") verbose := flags.Bool("v", false, "") + keyGen := flags.String("keygen", "", "") flags.Usage = func() { fmt.Print(serverHelp) @@ -194,6 +208,18 @@ func server(args []string) { } flags.Parse(args) + if *keyGen != "" { + if err := ccrypto.GenerateKeyFile(*keyGen, config.KeySeed); err != nil { + log.Fatal(err) + } + return + } + + if config.KeySeed != "" { + log.Print("Option `--key` is deprecated and will be removed in a future version of chisel.") + log.Print("Please use `chisel server --keygen /file/path`, followed by `chisel server --keyfile /file/path` to specify the SSH private key") + } + if *host == "" { *host = os.Getenv("HOST") } @@ -209,8 +235,13 @@ func server(args []string) { if *port == "" { *port = "8080" } - if config.KeySeed == "" { - config.KeySeed = os.Getenv("CHISEL_KEY") + if config.KeyFile == "" { + config.KeyFile = settings.Env("KEY_FILE") + } else if config.KeySeed == "" { + config.KeySeed = settings.Env("KEY") + } + if config.Auth == "" { + config.Auth = os.Getenv("AUTH") } s, err := chserver.NewServer(config) if err != nil { @@ -366,6 +397,9 @@ var clientHelp = ` --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). + --sni, Override the ServerName when using TLS (defaults to the + hostname). + --tls-ca, An optional root certificate bundle used to verify the chisel server. Only valid when connecting to the server with "https" or "wss". By default, the operating system CAs will be used. @@ -401,6 +435,7 @@ func client(args []string) { flags.StringVar(&config.TLS.Key, "tls-key", "", "") flags.Var(&headerFlags{config.Headers}, "header", "") hostname := flags.String("hostname", "", "") + sni := flags.String("sni", "", "") pid := flags.Bool("pid", false, "") verbose := flags.Bool("v", false, "") flags.Usage = func() { @@ -422,7 +457,13 @@ func client(args []string) { //move hostname onto headers if *hostname != "" { config.Headers.Set("Host", *hostname) + config.TLS.ServerName = *hostname } + + if *sni != "" { + config.TLS.ServerName = *sni + } + //ready c, err := chclient.NewClient(&config) if err != nil { diff --git a/server/server.go b/server/server.go index b7df1282..8a702fce 100644 --- a/server/server.go +++ b/server/server.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "os" "regexp" "time" @@ -23,6 +24,7 @@ import ( // Config is the configuration for the chisel service type Config struct { KeySeed string + KeyFile string AuthFile string Auth string Proxy string @@ -73,13 +75,38 @@ func NewServer(c *Config) (*Server, error) { server.users.AddUser(u) } } - //generate private key (optionally using seed) - key, err := ccrypto.GenerateKey(c.KeySeed) - if err != nil { - log.Fatal("Failed to generate key") + + var pemBytes []byte + var err error + if c.KeyFile != "" { + var key []byte + + if ccrypto.IsChiselKey([]byte(c.KeyFile)) { + key = []byte(c.KeyFile) + } else { + key, err = os.ReadFile(c.KeyFile) + if err != nil { + log.Fatalf("Failed to read key file %s", c.KeyFile) + } + } + + pemBytes = key + if ccrypto.IsChiselKey(key) { + pemBytes, err = ccrypto.ChiselKey2PEM(key) + if err != nil { + log.Fatalf("Invalid key %s", string(key)) + } + } + } else { + //generate private key (optionally using seed) + pemBytes, err = ccrypto.Seed2PEM(c.KeySeed) + if err != nil { + log.Fatal("Failed to generate key") + } } + //convert into ssh.PrivateKey - private, err := ssh.ParsePrivateKey(key) + private, err := ssh.ParsePrivateKey(pemBytes) if err != nil { log.Fatal("Failed to parse key") } diff --git a/server/server_handler.go b/server/server_handler.go index 85229e97..a732e2b6 100644 --- a/server/server_handler.go +++ b/server/server_handler.go @@ -19,7 +19,7 @@ func (s *Server) handleClientHandler(w http.ResponseWriter, r *http.Request) { //websockets upgrade AND has chisel prefix upgrade := strings.ToLower(r.Header.Get("Upgrade")) protocol := r.Header.Get("Sec-WebSocket-Protocol") - if upgrade == "websocket" && strings.HasPrefix(protocol, "chisel-") { + if upgrade == "websocket" { if protocol == chshare.ProtocolVersion { s.handleWebsocket(w, r) return @@ -34,7 +34,7 @@ func (s *Server) handleClientHandler(w http.ResponseWriter, r *http.Request) { return } //no proxy defined, provide access to health/version checks - switch r.URL.String() { + switch r.URL.Path { case "/health": w.Write([]byte("OK\n")) return @@ -101,13 +101,13 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) { return } //print if client and server versions dont match - if c.Version != chshare.BuildVersion { - v := c.Version - if v == "" { - v = "" - } - l.Infof("Client version (%s) differs from server version (%s)", - v, chshare.BuildVersion) + cv := strings.TrimPrefix(c.Version, "v") + if cv == "" { + cv = "" + } + sv := strings.TrimPrefix(chshare.BuildVersion, "v") + if cv != sv { + l.Infof("Client version (%s) differs from server version (%s)", cv, sv) } //validate remotes for _, r := range c.Remotes { diff --git a/server/server_listen.go b/server/server_listen.go index a7dcfe84..f6eb1ffa 100644 --- a/server/server_listen.go +++ b/server/server_listen.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "crypto/x509" "errors" - "io/ioutil" "net" "os" "os/user" @@ -116,7 +115,7 @@ func addCA(ca string, c *tls.Config) error { clientCAPool := x509.NewCertPool() if fileInfo.IsDir() { //this is a directory holding CA bundle files - files, err := ioutil.ReadDir(ca) + files, err := os.ReadDir(ca) if err != nil { return err } @@ -140,7 +139,7 @@ func addCA(ca string, c *tls.Config) error { } func addPEMFile(path string, pool *x509.CertPool) error { - content, err := ioutil.ReadFile(path) + content, err := os.ReadFile(path) if err != nil { return err } diff --git a/share/ccrypto/generate_key_go119.go b/share/ccrypto/generate_key_go119.go new file mode 100644 index 00000000..68e59e3f --- /dev/null +++ b/share/ccrypto/generate_key_go119.go @@ -0,0 +1,42 @@ +package ccrypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "io" + "math/big" +) + +var one = new(big.Int).SetInt64(1) + +// This function is copied from ecdsa.GenerateKey() of Go 1.19 +func GenerateKeyGo119(c elliptic.Curve, rand io.Reader) (*ecdsa.PrivateKey, error) { + k, err := randFieldElement(c, rand) + if err != nil { + return nil, err + } + + priv := new(ecdsa.PrivateKey) + priv.PublicKey.Curve = c + priv.D = k + priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes()) + return priv, nil +} + +// This function is copied from Go 1.19 +func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) { + params := c.Params() + // Note that for P-521 this will actually be 63 bits more than the order, as + // division rounds down, but the extra bit is inconsequential. + b := make([]byte, params.N.BitLen()/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(params.N, one) + k.Mod(k, n) + k.Add(k, one) + return +} diff --git a/share/ccrypto/keys.go b/share/ccrypto/keys.go index 91d875aa..d6d71305 100644 --- a/share/ccrypto/keys.go +++ b/share/ccrypto/keys.go @@ -1,36 +1,34 @@ package ccrypto import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/sha256" - "crypto/x509" "encoding/base64" - "encoding/pem" "fmt" + "os" "golang.org/x/crypto/ssh" ) -//GenerateKey for use as an SSH private key +// GenerateKey generates a PEM key func GenerateKey(seed string) ([]byte, error) { - r := rand.Reader - if seed != "" { - r = NewDetermRand([]byte(seed)) - } - priv, err := ecdsa.GenerateKey(elliptic.P256(), r) + return Seed2PEM(seed) +} + +// GenerateKeyFile generates an ChiselKey +func GenerateKeyFile(keyFilePath, seed string) error { + chiselKey, err := seed2ChiselKey(seed) if err != nil { - return nil, err + return err } - b, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return nil, fmt.Errorf("Unable to marshal ECDSA private key: %v", err) + + if keyFilePath == "-" { + fmt.Print(string(chiselKey)) + return nil } - return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}), nil + return os.WriteFile(keyFilePath, chiselKey, 0600) } -//FingerprintKey calculates the SHA256 hash of an SSH public key +// FingerprintKey calculates the SHA256 hash of an SSH public key func FingerprintKey(k ssh.PublicKey) string { bytes := sha256.Sum256(k.Marshal()) return base64.StdEncoding.EncodeToString(bytes[:]) diff --git a/share/ccrypto/keys_helpers.go b/share/ccrypto/keys_helpers.go new file mode 100644 index 00000000..5e21c8bd --- /dev/null +++ b/share/ccrypto/keys_helpers.go @@ -0,0 +1,97 @@ +package ccrypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "strings" +) + +const ChiselKeyPrefix = "ck-" + +// Relations between entities: +// +// .............> PEM <........... +// . ^ . +// . | . +// . | . +// Seed -------> PrivateKey . +// . ^ . +// . | . +// . V . +// ..........> ChiselKey ......... + +func Seed2PEM(seed string) ([]byte, error) { + privateKey, err := seed2PrivateKey(seed) + if err != nil { + return nil, err + } + + return privateKey2PEM(privateKey) +} + +func seed2ChiselKey(seed string) ([]byte, error) { + privateKey, err := seed2PrivateKey(seed) + if err != nil { + return nil, err + } + + return privateKey2ChiselKey(privateKey) +} + +func seed2PrivateKey(seed string) (*ecdsa.PrivateKey, error) { + if seed == "" { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + } else { + return GenerateKeyGo119(elliptic.P256(), NewDetermRand([]byte(seed))) + } +} + +func privateKey2ChiselKey(privateKey *ecdsa.PrivateKey) ([]byte, error) { + b, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return nil, err + } + + encodedPrivateKey := make([]byte, base64.RawStdEncoding.EncodedLen(len(b))) + base64.RawStdEncoding.Encode(encodedPrivateKey, b) + + return append([]byte(ChiselKeyPrefix), encodedPrivateKey...), nil +} + +func privateKey2PEM(privateKey *ecdsa.PrivateKey) ([]byte, error) { + b, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}), nil +} + +func chiselKey2PrivateKey(chiselKey []byte) (*ecdsa.PrivateKey, error) { + rawChiselKey := chiselKey[len(ChiselKeyPrefix):] + + decodedPrivateKey := make([]byte, base64.RawStdEncoding.DecodedLen(len(rawChiselKey))) + _, err := base64.RawStdEncoding.Decode(decodedPrivateKey, rawChiselKey) + if err != nil { + return nil, err + } + + return x509.ParseECPrivateKey(decodedPrivateKey) +} + +func ChiselKey2PEM(chiselKey []byte) ([]byte, error) { + privateKey, err := chiselKey2PrivateKey(chiselKey) + if err == nil { + return privateKey2PEM(privateKey) + } + + return nil, err +} + +func IsChiselKey(chiselKey []byte) bool { + return strings.HasPrefix(string(chiselKey), ChiselKeyPrefix) +} diff --git a/share/cio/stdio.go b/share/cio/stdio.go index 24798def..16327989 100644 --- a/share/cio/stdio.go +++ b/share/cio/stdio.go @@ -2,7 +2,6 @@ package cio import ( "io" - "io/ioutil" "os" ) @@ -11,6 +10,6 @@ var Stdio = &struct { io.ReadCloser io.Writer }{ - ioutil.NopCloser(os.Stdin), + io.NopCloser(os.Stdin), os.Stdout, } diff --git a/share/settings/env.go b/share/settings/env.go index 17b002be..53beefcc 100644 --- a/share/settings/env.go +++ b/share/settings/env.go @@ -3,15 +3,16 @@ package settings import ( "os" "strconv" + "strings" "time" ) -//Env returns a chisel environment variable +// Env returns a chisel environment variable func Env(name string) string { return os.Getenv("CHISEL_" + name) } -//EnvInt returns an integer using an environment variable, with a default fallback +// EnvInt returns an integer using an environment variable, with a default fallback func EnvInt(name string, def int) int { if n, err := strconv.Atoi(Env(name)); err == nil { return n @@ -19,10 +20,16 @@ func EnvInt(name string, def int) int { return def } -//EnvDuration returns a duration using an environment variable, with a default fallback +// EnvDuration returns a duration using an environment variable, with a default fallback func EnvDuration(name string, def time.Duration) time.Duration { if n, err := time.ParseDuration(Env(name)); err == nil { return n } return def } + +// EnvBool returns a boolean using an environment variable +func EnvBool(name string) bool { + v := Env(name) + return v == "1" || strings.ToLower(v) == "true" +} diff --git a/share/settings/users.go b/share/settings/users.go index ea57f280..a6f0a093 100644 --- a/share/settings/users.go +++ b/share/settings/users.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "regexp" "sync" @@ -125,7 +125,7 @@ func (u *UserIndex) loadUserIndex() error { if u.configFile == "" { return errors.New("configuration file not set") } - b, err := ioutil.ReadFile(u.configFile) + b, err := os.ReadFile(u.configFile) if err != nil { return fmt.Errorf("Failed to read auth file: %s, error: %s", u.configFile, err) } diff --git a/share/tunnel/tunnel.go b/share/tunnel/tunnel.go index 0cfba6fa..c22b737a 100644 --- a/share/tunnel/tunnel.go +++ b/share/tunnel/tunnel.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "errors" - "io/ioutil" + "io" "log" "os" "sync" @@ -57,7 +57,7 @@ func New(c Config) *Tunnel { //setup socks server (not listening on any port!) extra := "" if c.Socks { - sl := log.New(ioutil.Discard, "", 0) + sl := log.New(io.Discard, "", 0) if t.Logger.Debug { sl = log.New(os.Stdout, "[socks]", log.Ldate|log.Ltime) } diff --git a/share/tunnel/tunnel_in_proxy.go b/share/tunnel/tunnel_in_proxy.go index eebdfbb5..007fb0c7 100644 --- a/share/tunnel/tunnel_in_proxy.go +++ b/share/tunnel/tunnel_in_proxy.go @@ -4,6 +4,7 @@ import ( "context" "io" "net" + "sync" "github.com/jpillora/chisel/share/cio" "github.com/jpillora/chisel/share/settings" @@ -26,6 +27,7 @@ type Proxy struct { dialer net.Dialer tcp *net.TCPListener udp *udpListener + mu sync.Mutex } //NewProxy creates a Proxy @@ -122,8 +124,12 @@ func (p *Proxy) runTCP(ctx context.Context) error { func (p *Proxy) pipeRemote(ctx context.Context, src io.ReadWriteCloser) { defer src.Close() + + p.mu.Lock() p.count++ cid := p.count + p.mu.Unlock() + l := p.Fork("conn#%d", cid) l.Debugf("Open") sshConn := p.sshTun.getSSH(ctx) diff --git a/share/tunnel/tunnel_in_proxy_udp.go b/share/tunnel/tunnel_in_proxy_udp.go index e2be43e2..f97a5dff 100644 --- a/share/tunnel/tunnel_in_proxy_udp.go +++ b/share/tunnel/tunnel_in_proxy_udp.go @@ -18,18 +18,19 @@ import ( "golang.org/x/sync/errgroup" ) -//listenUDP is a special listener which forwards packets via -//the bound ssh connection. tricky part is multiplexing lots of -//udp clients through the entry node. each will listen on its -//own source-port for a response: -// (random) -// src-1 1111->... dst-1 6345->7777 -// src-2 2222->... <---> udp <---> udp <-> dst-1 7543->7777 -// src-3 3333->... listener handler dst-1 1444->7777 +// listenUDP is a special listener which forwards packets via +// the bound ssh connection. tricky part is multiplexing lots of +// udp clients through the entry node. each will listen on its +// own source-port for a response: // -//we must store these mappings (1111-6345, etc) in memory for a length -//of time, so that when the exit node receives a response on 6345, it -//knows to return it to 1111. +// (random) +// src-1 1111->... dst-1 6345->7777 +// src-2 2222->... <---> udp <---> udp <-> dst-1 7543->7777 +// src-3 3333->... listener handler dst-1 1444->7777 +// +// we must store these mappings (1111-6345, etc) in memory for a length +// of time, so that when the exit node receives a response on 6345, it +// knows to return it to 1111. func listenUDP(l *cio.Logger, sshTun sshTunnel, remote *settings.Remote) (*udpListener, error) { a, err := net.ResolveUDPAddr("udp", remote.Local()) if err != nil { @@ -45,7 +46,9 @@ func listenUDP(l *cio.Logger, sshTun sshTunnel, remote *settings.Remote) (*udpLi sshTun: sshTun, remote: remote, inbound: conn, + maxMTU: settings.EnvInt("UDP_MAX_SIZE", 9012), } + u.Debugf("UDP max size: %d bytes", u.maxMTU) return u, nil } @@ -57,6 +60,7 @@ type udpListener struct { outboundMut sync.Mutex outbound *udpChannel sent, recv int64 + maxMTU int } func (u *udpListener) run(ctx context.Context) error { @@ -80,8 +84,7 @@ func (u *udpListener) run(ctx context.Context) error { } func (u *udpListener) runInbound(ctx context.Context) error { - maxMTU := settings.EnvInt("UDP_MAX_SIZE", 12000) - buff := make([]byte, maxMTU) + buff := make([]byte, u.maxMTU) for !isDone(ctx) { //read from inbound udp u.inbound.SetReadDeadline(time.Now().Add(time.Second)) diff --git a/share/tunnel/tunnel_out_ssh_udp.go b/share/tunnel/tunnel_out_ssh_udp.go index 5f274927..d3c4c62f 100644 --- a/share/tunnel/tunnel_out_ssh_udp.go +++ b/share/tunnel/tunnel_out_ssh_udp.go @@ -27,7 +27,9 @@ func (t *Tunnel) handleUDP(l *cio.Logger, rwc io.ReadWriteCloser, hostPort strin c: rwc, }, udpConns: conns, + maxMTU: settings.EnvInt("UDP_MAX_SIZE", 9012), } + h.Debugf("UDP max size: %d bytes", h.maxMTU) for { p := udpPacket{} if err := h.handleWrite(&p); err != nil { @@ -41,6 +43,7 @@ type udpHandler struct { hostPort string *udpChannel *udpConns + maxMTU int } func (h *udpHandler) handleWrite(p *udpPacket) error { @@ -77,8 +80,7 @@ func (h *udpHandler) handleWrite(p *udpPacket) error { func (h *udpHandler) handleRead(p *udpPacket, conn *udpConn) { //ensure connection is cleaned up defer h.udpConns.remove(conn.id) - maxMTU := settings.EnvInt("UDP_MAX_SIZE", 12000) - buff := make([]byte, maxMTU) + buff := make([]byte, h.maxMTU) for { //response must arrive within 15 seconds deadline := settings.EnvDuration("UDP_DEADLINE", 15*time.Second) diff --git a/share/version.go b/share/version.go index 46efda9b..f503f85c 100644 --- a/share/version.go +++ b/share/version.go @@ -4,6 +4,6 @@ package chshare //incompatible changes are made, this will //be incremented to signify a protocol //mismatch. -const ProtocolVersion = "chisel-v3" +var ProtocolVersion = "chisel-v3" var BuildVersion = "0.0.0-src" diff --git a/test/bench/main.go b/test/bench/main.go index 091f2d5d..f47b10c1 100644 --- a/test/bench/main.go +++ b/test/bench/main.go @@ -17,7 +17,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" "net/http" "os" @@ -86,7 +85,7 @@ func testTunnel(port string, size int) { fatal(err) } - n, err := io.Copy(ioutil.Discard, resp.Body) + n, err := io.Copy(io.Discard, resp.Body) if err != nil { fatal(err) } diff --git a/test/e2e/cert_utils_test.go b/test/e2e/cert_utils_test.go new file mode 100644 index 00000000..f71a13dd --- /dev/null +++ b/test/e2e/cert_utils_test.go @@ -0,0 +1,256 @@ +package e2e_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "os" + "path" + "time" + + chclient "github.com/jpillora/chisel/client" + chserver "github.com/jpillora/chisel/server" +) + +type tlsConfig struct { + serverTLS *chserver.TLSConfig + clientTLS *chclient.TLSConfig + tmpDir string +} + +func (t *tlsConfig) Close() { + if t.tmpDir != "" { + os.RemoveAll(t.tmpDir) + } +} + +func newTestTLSConfig() (*tlsConfig, error) { + tlsConfig := &tlsConfig{} + _, serverCertPEM, serverKeyPEM, err := certGetCertificate(&certConfig{ + hosts: []string{ + "0.0.0.0", + "localhost", + }, + extKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return nil, err + } + _, clientCertPEM, clientKeyPEM, err := certGetCertificate(&certConfig{ + extKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }) + if err != nil { + return nil, err + } + + tlsConfig.tmpDir, err = os.MkdirTemp("", "") + if err != nil { + return nil, err + } + + dirServerCA := path.Join(tlsConfig.tmpDir, "server-ca") + if err := os.Mkdir(dirServerCA, 0777); err != nil { + return nil, err + } + pathServerCACrt := path.Join(dirServerCA, "client.crt") + if err := os.WriteFile(pathServerCACrt, clientCertPEM, 0666); err != nil { + return nil, err + } + + dirClientCA := path.Join(tlsConfig.tmpDir, "client-ca") + if err := os.Mkdir(dirClientCA, 0777); err != nil { + return nil, err + } + pathClientCACrt := path.Join(dirClientCA, "server.crt") + if err := os.WriteFile(pathClientCACrt, serverCertPEM, 0666); err != nil { + return nil, err + } + + dirServerCrt := path.Join(tlsConfig.tmpDir, "server-crt") + if err := os.Mkdir(dirServerCrt, 0777); err != nil { + return nil, err + } + pathServerCrtCrt := path.Join(dirServerCrt, "server.crt") + if err := os.WriteFile(pathServerCrtCrt, serverCertPEM, 0666); err != nil { + return nil, err + } + pathServerCrtKey := path.Join(dirServerCrt, "server.key") + if err := os.WriteFile(pathServerCrtKey, serverKeyPEM, 0666); err != nil { + return nil, err + } + + dirClientCrt := path.Join(tlsConfig.tmpDir, "client-crt") + if err := os.Mkdir(dirClientCrt, 0777); err != nil { + return nil, err + } + pathClientCrtCrt := path.Join(dirClientCrt, "client.crt") + if err := os.WriteFile(pathClientCrtCrt, clientCertPEM, 0666); err != nil { + return nil, err + } + pathClientCrtKey := path.Join(dirClientCrt, "client.key") + if err := os.WriteFile(pathClientCrtKey, clientKeyPEM, 0666); err != nil { + return nil, err + } + + // for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert + tlsConfig.serverTLS = &chserver.TLSConfig{ + CA: pathServerCACrt, + Cert: pathServerCrtCrt, + Key: pathServerCrtKey, + } + tlsConfig.clientTLS = &chclient.TLSConfig{ + CA: pathClientCACrt, + Cert: pathClientCrtCrt, + Key: pathClientCrtKey, + } + return tlsConfig, nil +} + +type certConfig struct { + signCA *x509.Certificate + isCA bool + hosts []string + validFrom *time.Time + validFor *time.Time + extKeyUsage []x509.ExtKeyUsage + rsaBits int + ecdsaCurve string + ed25519Key bool +} + +func certGetCertificate(c *certConfig) (*x509.Certificate, []byte, []byte, error) { + var err error + var priv interface{} + switch c.ecdsaCurve { + case "": + if c.ed25519Key { + _, priv, err = ed25519.GenerateKey(rand.Reader) + } else { + rsaBits := c.rsaBits + if rsaBits == 0 { + rsaBits = 2048 + } + priv, err = rsa.GenerateKey(rand.Reader, rsaBits) + } + case "P224": + priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + case "P256": + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case "P384": + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case "P521": + priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + default: + return nil, nil, nil, fmt.Errorf("Unrecognized elliptic curve: %q", c.ecdsaCurve) + } + if err != nil { + return nil, nil, nil, fmt.Errorf("Failed to generate private key: %v", err) + } + + // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature + // KeyUsage bits set in the x509.Certificate template + keyUsage := x509.KeyUsageDigitalSignature + // Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In + // the context of TLS this KeyUsage is particular to RSA key exchange and + // authentication. + if _, isRSA := priv.(*rsa.PrivateKey); isRSA { + keyUsage |= x509.KeyUsageKeyEncipherment + } + + notBefore := time.Now() + if c.validFrom != nil { + notBefore = *c.validFrom + } + + notAfter := time.Now().Add(24 * time.Hour) + if c.validFor != nil { + notAfter = *c.validFor + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, nil, fmt.Errorf("Failed to generate serial number: %v", err) + } + + cert := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + OrganizationalUnit: []string{"test"}, + Organization: []string{"Chisel"}, + Country: []string{"us"}, + Province: []string{"ma"}, + Locality: []string{"Boston"}, + CommonName: "localhost", + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage, + ExtKeyUsage: c.extKeyUsage, + BasicConstraintsValid: true, + } + + for _, h := range c.hosts { + if ip := net.ParseIP(h); ip != nil { + cert.IPAddresses = append(cert.IPAddresses, ip) + } else { + cert.DNSNames = append(cert.DNSNames, h) + } + } + + if c.isCA { + cert.IsCA = true + cert.KeyUsage |= x509.KeyUsageCertSign + } + + ca := cert + if c.signCA != nil { + ca = c.signCA + } + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, certGetPublicKey(priv), priv) + if err != nil { + return nil, nil, nil, fmt.Errorf("Failed to create certificate: %v", err) + } + + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to marshal private key: %v", err) + } + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privBytes, + }) + + return cert, certPEM.Bytes(), certPrivKeyPEM.Bytes(), nil +} + +func certGetPublicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + case ed25519.PrivateKey: + return k.Public().(ed25519.PublicKey) + default: + return nil + } +} diff --git a/test/e2e/setup_test.go b/test/e2e/setup_test.go index a01e119c..c1611bfb 100644 --- a/test/e2e/setup_test.go +++ b/test/e2e/setup_test.go @@ -2,7 +2,7 @@ package e2e_test import ( "context" - "io/ioutil" + "io" "log" "net" "net/http" @@ -16,7 +16,7 @@ import ( const debug = true -//test layout configuration +// test layout configuration type testLayout struct { server *chserver.Config client *chclient.Config @@ -36,7 +36,7 @@ func (tl *testLayout) setup(t *testing.T) (server *chserver.Server, client *chcl fileAddr := "127.0.0.1:" + filePort f := http.Server{ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := ioutil.ReadAll(r.Body) + b, _ := io.ReadAll(r.Body) w.Write(append(b, '!')) }), } @@ -133,7 +133,7 @@ func post(url, body string) (string, error) { if err != nil { return "", err } - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if err != nil { return "", err } diff --git a/test/e2e/tls/client-ca/server.crt b/test/e2e/tls/client-ca/server.crt deleted file mode 100644 index 03d30135..00000000 --- a/test/e2e/tls/client-ca/server.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIULHkiMif/ugoAO0KWSPj7OW1gfP0wDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAlVLMQ8wDQYDVQQHDAZMb25kb24x -DzANBgNVBAoMBmNoaXNlbDEPMA0GA1UECwwGY2hpc2VsMRIwEAYDVQQDDAlsb2Nh -bGhvc3QwIBcNMjIxMTI5MTIwNDUxWhgPMjA1MDA0MTUxMjA0NTFaMGExCzAJBgNV -BAYTAlVLMQswCQYDVQQIDAJVSzEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZj -aGlzZWwxDzANBgNVBAsMBmNoaXNlbDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhJwtemnuvV/4AAzbGLm/wqVx8as -537o87VoA237jM6pEI4/fpkqla9e15kPJdAHE3zPo4OiuFTYEF4n6WoEDYQIrRp/ -vVnPDL6EqcmG5y0kf2LVbjElNNiy+JLhT+rjTlBADXQKDbBQmoxspokg9qAIkiyK -6Xi+0WAvKK7wjwoa5+lXyAhAfaYaHEA1W/6hBbiW+v5OHTISzA1L3Y/GJ8jtdWmm -ovcGUssXXXgXX0/fm7YuRhtdqVC6n3DOgY2L29A5WxjcwDvfIJwncORydNOOgSBW -b9mc2ePs/bL1U8l72luBUxmE/EA1D15N1t/WMd9dWdaSZ3W0riBI6751NwIDAQAB -oz0wOzAaBgNVHREEEzARhwR/AAABgglsb2NhbGhvc3QwHQYDVR0OBBYEFLdSd2YA -VcC/DxvMXloPVFSPENS3MA0GCSqGSIb3DQEBCwUAA4IBAQBMYbkwsimYqplnWiqv -o58dYjXJWsazecsk1azktoLXUA0de6jJz7kXDKRnITQ3JwpDRpdnYnudAUZwdDKS -grElCx2eAsiD/M9KY7hvYJ7TUzdbrGqDp0M6K61PD4WYAicjSQdzx274gwGEZwcZ -LdWYeo+xY0fbr5abKmZRDPfcoo9TLVOitSSw83dSlFpMWux7QUYXzzIXbQ3TvN2d -j1M1NeBfAy5wUUQwv497ZtRKVEdsjpktWY4dak/+fNMKsKIg0PvN3huy56zYvNN+ -P1vCKBS/q3NO/dTcKz9YmlNFwWwgHcxsRjRJV/+AUW1U131uVtkkavgr4LxR8F9/ -+VKn ------END CERTIFICATE----- diff --git a/test/e2e/tls/client-crt/client.crt b/test/e2e/tls/client-crt/client.crt deleted file mode 100644 index b332ae5c..00000000 --- a/test/e2e/tls/client-crt/client.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUQ+jl8wl4zp65MoT60Tc8pBR8gvIwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAlVLMQ8wDQYDVQQHDAZMb25kb24x -DzANBgNVBAoMBmNoaXNlbDEPMA0GA1UECwwGY2hpc2VsMRIwEAYDVQQDDAlsb2Nh -bGhvc3QwIBcNMjIxMTI5MTIwNTAyWhgPMjA1MDA0MTUxMjA1MDJaMGExCzAJBgNV -BAYTAlVLMQswCQYDVQQIDAJVSzEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZj -aGlzZWwxDzANBgNVBAsMBmNoaXNlbDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhJwtemnuvV/4AAzbGLm/wqVx8as -537o87VoA237jM6pEI4/fpkqla9e15kPJdAHE3zPo4OiuFTYEF4n6WoEDYQIrRp/ -vVnPDL6EqcmG5y0kf2LVbjElNNiy+JLhT+rjTlBADXQKDbBQmoxspokg9qAIkiyK -6Xi+0WAvKK7wjwoa5+lXyAhAfaYaHEA1W/6hBbiW+v5OHTISzA1L3Y/GJ8jtdWmm -ovcGUssXXXgXX0/fm7YuRhtdqVC6n3DOgY2L29A5WxjcwDvfIJwncORydNOOgSBW -b9mc2ePs/bL1U8l72luBUxmE/EA1D15N1t/WMd9dWdaSZ3W0riBI6751NwIDAQAB -oz0wOzAaBgNVHREEEzARhwR/AAABgglsb2NhbGhvc3QwHQYDVR0OBBYEFLdSd2YA -VcC/DxvMXloPVFSPENS3MA0GCSqGSIb3DQEBCwUAA4IBAQCposNDi32S1ODRn1Rs -x3rmrXGKOZp48qwxVYmBdxb1iqrXVupkmKpeHi4YZeuvauFIWNSgW+bEAvREeccZ -fFmoanAxFcyYYIIyst/GrJPmx5q/jtNUOdA5shvvv1kRehddomHO5SjoplGxpyWv -23B6r322ggs+Wq0fcMv8kfFmJfCSVUIQ0kt1kOTLvVxlr2NSvyfFeHZHjBCkuDbD -Rw2ADxX9rpZkGKOIrdoEiInSmjJYyLrrtxffgu8+pU8trcxw1hu5qNrK04S6IsQ0 -YYzE6lTXnRdkR+42EPk4OpZXULhWgkYyPtvq0qiqV5pAMOmsMm9eJWcdv7SEIMbg -kgLh ------END CERTIFICATE----- diff --git a/test/e2e/tls/client-crt/client.key b/test/e2e/tls/client-crt/client.key deleted file mode 100644 index f1e59a60..00000000 --- a/test/e2e/tls/client-crt/client.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCEnC16ae69X/g -ADNsYub/CpXHxqznfujztWgDbfuMzqkQjj9+mSqVr17XmQ8l0AcTfM+jg6K4VNgQ -XifpagQNhAitGn+9Wc8MvoSpyYbnLSR/YtVuMSU02LL4kuFP6uNOUEANdAoNsFCa -jGymiSD2oAiSLIrpeL7RYC8orvCPChrn6VfICEB9phocQDVb/qEFuJb6/k4dMhLM -DUvdj8YnyO11aaai9wZSyxddeBdfT9+bti5GG12pULqfcM6BjYvb0DlbGNzAO98g -nCdw5HJ0046BIFZv2ZzZ4+z9svVTyXvaW4FTGYT8QDUPXk3W39Yx311Z1pJndbSu -IEjrvnU3AgMBAAECggEACLy4y0/SJ9YepDCOIB6RxMCuvphAt4lcpoAvxxu/+/Zf -4SluTV5K7wM+s1kmN+8WKnfvhy6JeGAFfDMo+9a0iQE9PMHZJ2awLiQzp8s1YhoD -ryJ9dmELCkaJRCykrSrngeKWB6UGQNiDwHl6+MJqs/+/cwlDVcCEYrvAU8YQeXgI -bOzffyLXgXbYm9epOEAZ2WJjrCKnOob15jvIS/OIVM+7a85y2WW8r9llRerWUiw1 -WgXXPssOGky7nFIT2O5f8IpGFSNVThmcLA5bAuCswbtL0rFRPvPjzixxG3w0XMy9 -FwsOXTiQMSmmsd86ZLb4dgi8QtQq+ai4e31kN3PXUQKBgQDc0RRCmP4DDvwJS9i/ -/f279uZ/zsF7mU5o5C+RlrJBqyW1f55IeP6yuP/0HoEqIiyZoVuexPIZwIQ0E0sq -/UY5x2ACIiOSqj0Lm389xp0BbaUZGw+skhi3xx6/5DY6BAJgsexxnRNXgODugHkX -Z4Beg0Yk3c+oy8uIqDqiLiO65wKBgQDg/ngIu3CRIkfXZxdpkymfTNZAFJDdsTUk -ccNkUNurwvR/Rw+jam3MiyqcpXfAgriByEYPjtgP8yNilCgNt10nOa9064caU92+ -xMvIHDqLlhQqADv27vbtT3v3LnUQ8sHHlC7oH0a9A9Lb4PA1cTTv+zHmvw8g+qfG -DlMAKlv5MQKBgEVG7zVJrH+nJtl/yzvI4t8iwgQdCMTPPzNXS7lAbyJ9IHKF3FHe -bAIEWoDrRRjD6fw1DHqOTWpY5OE1oGKXXX3pGCzzKvfrowVOPqhR9JYrIR30SIGa -wFiABxyq63xeH6TYjVbDsfrE+0y4T8VQ+owsYUhVsr3BWlTAeszA7wLHAoGBAISs -yi+sCGctJdwZiK7S/vpXHwhOmFaChunkw+nF3KPepLrhNpGQ0wqJDimtUX6OFytA -5D+MZlCRec5Ju4zLEf1tgM+bi+G0jtBN0DVCoCtr5pwV40ZsB+RMuLc8CABkhmyg -L0DmIJqZRKAwdrWs+iCBqh19kPhLlt+t1rgEQpQhAoGAc3yinIJ91rZzQxkM+n0Z -0ughaAj1DJPxfnwKyGKzJK7jjfpbU+hvUjw6y82ZqhuRIFC5Qyn6E84Kx+0VOKCd -QMNuOXCNzxy6f5tLBgsmEoDh6yO0N45DE6Iowr677SvCnbF1vqmR9BBHct0GtPha -uGZ12m4UwbNWX6ltEHPkO/w= ------END PRIVATE KEY----- diff --git a/test/e2e/tls/server-ca/client.crt b/test/e2e/tls/server-ca/client.crt deleted file mode 100644 index b332ae5c..00000000 --- a/test/e2e/tls/server-ca/client.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUQ+jl8wl4zp65MoT60Tc8pBR8gvIwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAlVLMQ8wDQYDVQQHDAZMb25kb24x -DzANBgNVBAoMBmNoaXNlbDEPMA0GA1UECwwGY2hpc2VsMRIwEAYDVQQDDAlsb2Nh -bGhvc3QwIBcNMjIxMTI5MTIwNTAyWhgPMjA1MDA0MTUxMjA1MDJaMGExCzAJBgNV -BAYTAlVLMQswCQYDVQQIDAJVSzEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZj -aGlzZWwxDzANBgNVBAsMBmNoaXNlbDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhJwtemnuvV/4AAzbGLm/wqVx8as -537o87VoA237jM6pEI4/fpkqla9e15kPJdAHE3zPo4OiuFTYEF4n6WoEDYQIrRp/ -vVnPDL6EqcmG5y0kf2LVbjElNNiy+JLhT+rjTlBADXQKDbBQmoxspokg9qAIkiyK -6Xi+0WAvKK7wjwoa5+lXyAhAfaYaHEA1W/6hBbiW+v5OHTISzA1L3Y/GJ8jtdWmm -ovcGUssXXXgXX0/fm7YuRhtdqVC6n3DOgY2L29A5WxjcwDvfIJwncORydNOOgSBW -b9mc2ePs/bL1U8l72luBUxmE/EA1D15N1t/WMd9dWdaSZ3W0riBI6751NwIDAQAB -oz0wOzAaBgNVHREEEzARhwR/AAABgglsb2NhbGhvc3QwHQYDVR0OBBYEFLdSd2YA -VcC/DxvMXloPVFSPENS3MA0GCSqGSIb3DQEBCwUAA4IBAQCposNDi32S1ODRn1Rs -x3rmrXGKOZp48qwxVYmBdxb1iqrXVupkmKpeHi4YZeuvauFIWNSgW+bEAvREeccZ -fFmoanAxFcyYYIIyst/GrJPmx5q/jtNUOdA5shvvv1kRehddomHO5SjoplGxpyWv -23B6r322ggs+Wq0fcMv8kfFmJfCSVUIQ0kt1kOTLvVxlr2NSvyfFeHZHjBCkuDbD -Rw2ADxX9rpZkGKOIrdoEiInSmjJYyLrrtxffgu8+pU8trcxw1hu5qNrK04S6IsQ0 -YYzE6lTXnRdkR+42EPk4OpZXULhWgkYyPtvq0qiqV5pAMOmsMm9eJWcdv7SEIMbg -kgLh ------END CERTIFICATE----- diff --git a/test/e2e/tls/server-crt/server.crt b/test/e2e/tls/server-crt/server.crt deleted file mode 100644 index 03d30135..00000000 --- a/test/e2e/tls/server-crt/server.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIULHkiMif/ugoAO0KWSPj7OW1gfP0wDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVUsxCzAJBgNVBAgMAlVLMQ8wDQYDVQQHDAZMb25kb24x -DzANBgNVBAoMBmNoaXNlbDEPMA0GA1UECwwGY2hpc2VsMRIwEAYDVQQDDAlsb2Nh -bGhvc3QwIBcNMjIxMTI5MTIwNDUxWhgPMjA1MDA0MTUxMjA0NTFaMGExCzAJBgNV -BAYTAlVLMQswCQYDVQQIDAJVSzEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZj -aGlzZWwxDzANBgNVBAsMBmNoaXNlbDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhJwtemnuvV/4AAzbGLm/wqVx8as -537o87VoA237jM6pEI4/fpkqla9e15kPJdAHE3zPo4OiuFTYEF4n6WoEDYQIrRp/ -vVnPDL6EqcmG5y0kf2LVbjElNNiy+JLhT+rjTlBADXQKDbBQmoxspokg9qAIkiyK -6Xi+0WAvKK7wjwoa5+lXyAhAfaYaHEA1W/6hBbiW+v5OHTISzA1L3Y/GJ8jtdWmm -ovcGUssXXXgXX0/fm7YuRhtdqVC6n3DOgY2L29A5WxjcwDvfIJwncORydNOOgSBW -b9mc2ePs/bL1U8l72luBUxmE/EA1D15N1t/WMd9dWdaSZ3W0riBI6751NwIDAQAB -oz0wOzAaBgNVHREEEzARhwR/AAABgglsb2NhbGhvc3QwHQYDVR0OBBYEFLdSd2YA -VcC/DxvMXloPVFSPENS3MA0GCSqGSIb3DQEBCwUAA4IBAQBMYbkwsimYqplnWiqv -o58dYjXJWsazecsk1azktoLXUA0de6jJz7kXDKRnITQ3JwpDRpdnYnudAUZwdDKS -grElCx2eAsiD/M9KY7hvYJ7TUzdbrGqDp0M6K61PD4WYAicjSQdzx274gwGEZwcZ -LdWYeo+xY0fbr5abKmZRDPfcoo9TLVOitSSw83dSlFpMWux7QUYXzzIXbQ3TvN2d -j1M1NeBfAy5wUUQwv497ZtRKVEdsjpktWY4dak/+fNMKsKIg0PvN3huy56zYvNN+ -P1vCKBS/q3NO/dTcKz9YmlNFwWwgHcxsRjRJV/+AUW1U131uVtkkavgr4LxR8F9/ -+VKn ------END CERTIFICATE----- diff --git a/test/e2e/tls/server-crt/server.key b/test/e2e/tls/server-crt/server.key deleted file mode 100644 index f1e59a60..00000000 --- a/test/e2e/tls/server-crt/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCEnC16ae69X/g -ADNsYub/CpXHxqznfujztWgDbfuMzqkQjj9+mSqVr17XmQ8l0AcTfM+jg6K4VNgQ -XifpagQNhAitGn+9Wc8MvoSpyYbnLSR/YtVuMSU02LL4kuFP6uNOUEANdAoNsFCa -jGymiSD2oAiSLIrpeL7RYC8orvCPChrn6VfICEB9phocQDVb/qEFuJb6/k4dMhLM -DUvdj8YnyO11aaai9wZSyxddeBdfT9+bti5GG12pULqfcM6BjYvb0DlbGNzAO98g -nCdw5HJ0046BIFZv2ZzZ4+z9svVTyXvaW4FTGYT8QDUPXk3W39Yx311Z1pJndbSu -IEjrvnU3AgMBAAECggEACLy4y0/SJ9YepDCOIB6RxMCuvphAt4lcpoAvxxu/+/Zf -4SluTV5K7wM+s1kmN+8WKnfvhy6JeGAFfDMo+9a0iQE9PMHZJ2awLiQzp8s1YhoD -ryJ9dmELCkaJRCykrSrngeKWB6UGQNiDwHl6+MJqs/+/cwlDVcCEYrvAU8YQeXgI -bOzffyLXgXbYm9epOEAZ2WJjrCKnOob15jvIS/OIVM+7a85y2WW8r9llRerWUiw1 -WgXXPssOGky7nFIT2O5f8IpGFSNVThmcLA5bAuCswbtL0rFRPvPjzixxG3w0XMy9 -FwsOXTiQMSmmsd86ZLb4dgi8QtQq+ai4e31kN3PXUQKBgQDc0RRCmP4DDvwJS9i/ -/f279uZ/zsF7mU5o5C+RlrJBqyW1f55IeP6yuP/0HoEqIiyZoVuexPIZwIQ0E0sq -/UY5x2ACIiOSqj0Lm389xp0BbaUZGw+skhi3xx6/5DY6BAJgsexxnRNXgODugHkX -Z4Beg0Yk3c+oy8uIqDqiLiO65wKBgQDg/ngIu3CRIkfXZxdpkymfTNZAFJDdsTUk -ccNkUNurwvR/Rw+jam3MiyqcpXfAgriByEYPjtgP8yNilCgNt10nOa9064caU92+ -xMvIHDqLlhQqADv27vbtT3v3LnUQ8sHHlC7oH0a9A9Lb4PA1cTTv+zHmvw8g+qfG -DlMAKlv5MQKBgEVG7zVJrH+nJtl/yzvI4t8iwgQdCMTPPzNXS7lAbyJ9IHKF3FHe -bAIEWoDrRRjD6fw1DHqOTWpY5OE1oGKXXX3pGCzzKvfrowVOPqhR9JYrIR30SIGa -wFiABxyq63xeH6TYjVbDsfrE+0y4T8VQ+owsYUhVsr3BWlTAeszA7wLHAoGBAISs -yi+sCGctJdwZiK7S/vpXHwhOmFaChunkw+nF3KPepLrhNpGQ0wqJDimtUX6OFytA -5D+MZlCRec5Ju4zLEf1tgM+bi+G0jtBN0DVCoCtr5pwV40ZsB+RMuLc8CABkhmyg -L0DmIJqZRKAwdrWs+iCBqh19kPhLlt+t1rgEQpQhAoGAc3yinIJ91rZzQxkM+n0Z -0ughaAj1DJPxfnwKyGKzJK7jjfpbU+hvUjw6y82ZqhuRIFC5Qyn6E84Kx+0VOKCd -QMNuOXCNzxy6f5tLBgsmEoDh6yO0N45DE6Iowr677SvCnbF1vqmR9BBHct0GtPha -uGZ12m4UwbNWX6ltEHPkO/w= ------END PRIVATE KEY----- diff --git a/test/e2e/tls_test.go b/test/e2e/tls_test.go index 593476e8..304fd937 100644 --- a/test/e2e/tls_test.go +++ b/test/e2e/tls_test.go @@ -1,6 +1,7 @@ package e2e_test import ( + "path" "testing" chclient "github.com/jpillora/chisel/client" @@ -8,25 +9,22 @@ import ( ) func TestTLS(t *testing.T) { + tlsConfig, err := newTestTLSConfig() + if err != nil { + t.Fatal(err) + } + defer tlsConfig.Close() + tmpPort := availablePort() //setup server, client, fileserver teardown := simpleSetup(t, &chserver.Config{ - TLS: chserver.TLSConfig{ - Cert: "tls/server-crt/server.crt", - Key: "tls/server-crt/server.key", - CA: "tls/server-ca/client.crt", - }, + TLS: *tlsConfig.serverTLS, }, &chclient.Config{ Remotes: []string{tmpPort + ":$FILEPORT"}, - TLS: chclient.TLSConfig{ - //for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert - CA: "tls/client-ca/server.crt", - Cert: "tls/client-crt/client.crt", - Key: "tls/client-crt/client.key", - }, - Server: "https://localhost:" + tmpPort, + TLS: *tlsConfig.clientTLS, + Server: "https://localhost:" + tmpPort, }) defer teardown() //test remote @@ -40,25 +38,24 @@ func TestTLS(t *testing.T) { } func TestMTLS(t *testing.T) { + tlsConfig, err := newTestTLSConfig() + if err != nil { + t.Fatal(err) + } + defer tlsConfig.Close() + //provide no client cert, server should reject the client request + tlsConfig.serverTLS.CA = path.Dir(tlsConfig.serverTLS.CA) + tmpPort := availablePort() //setup server, client, fileserver teardown := simpleSetup(t, &chserver.Config{ - TLS: chserver.TLSConfig{ - CA: "tls/server-ca", - Cert: "tls/server-crt/server.crt", - Key: "tls/server-crt/server.key", - }, + TLS: *tlsConfig.serverTLS, }, &chclient.Config{ Remotes: []string{tmpPort + ":$FILEPORT"}, - TLS: chclient.TLSConfig{ - //for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert - CA: "tls/client-ca/server.crt", - Cert: "tls/client-crt/client.crt", - Key: "tls/client-crt/client.key", - }, - Server: "https://localhost:" + tmpPort, + TLS: *tlsConfig.clientTLS, + Server: "https://localhost:" + tmpPort, }) defer teardown() //test remote @@ -72,60 +69,59 @@ func TestMTLS(t *testing.T) { } func TestTLSMissingClientCert(t *testing.T) { + tlsConfig, err := newTestTLSConfig() + if err != nil { + t.Fatal(err) + } + defer tlsConfig.Close() + //provide no client cert, server should reject the client request + tlsConfig.clientTLS.Cert = "" + tlsConfig.clientTLS.Key = "" + tmpPort := availablePort() //setup server, client, fileserver teardown := simpleSetup(t, &chserver.Config{ - TLS: chserver.TLSConfig{ - CA: "tls/server-ca/client.crt", - Cert: "tls/server-crt/server.crt", - Key: "tls/server-crt/server.key", - }, + TLS: *tlsConfig.serverTLS, }, &chclient.Config{ Remotes: []string{tmpPort + ":$FILEPORT"}, - TLS: chclient.TLSConfig{ - CA: "tls/client-ca/server.crt", - //provide no client cert, server should reject the client request - //Cert: "tls/client-crt/client.crt", - //Key: "tls/client-crt/client.key", - }, - Server: "https://localhost:" + tmpPort, + TLS: *tlsConfig.clientTLS, + Server: "https://localhost:" + tmpPort, }) defer teardown() //test remote - _, err := post("http://localhost:"+tmpPort, "foo") + _, err = post("http://localhost:"+tmpPort, "foo") if err == nil { t.Fatal(err) } } func TestTLSMissingClientCA(t *testing.T) { + tlsConfig, err := newTestTLSConfig() + if err != nil { + t.Fatal(err) + } + defer tlsConfig.Close() + //specify a CA which does not match the client cert + //server should reject the client request + //provide no client cert, server should reject the client request + tlsConfig.serverTLS.CA = tlsConfig.clientTLS.CA + tmpPort := availablePort() //setup server, client, fileserver teardown := simpleSetup(t, &chserver.Config{ - TLS: chserver.TLSConfig{ - //specify a CA which does not match the client cert - //server should reject the client request - CA: "tls/server-crt/server.crt", - Cert: "tls/server-crt/server.crt", - Key: "tls/server-crt/server.key", - }, + TLS: *tlsConfig.serverTLS, }, &chclient.Config{ Remotes: []string{tmpPort + ":$FILEPORT"}, - TLS: chclient.TLSConfig{ - //for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert - CA: "tls/client-ca/server.crt", - Cert: "tls/client-crt/client.crt", - Key: "tls/client-crt/client.key", - }, - Server: "https://localhost:" + tmpPort, + TLS: *tlsConfig.clientTLS, + Server: "https://localhost:" + tmpPort, }) defer teardown() //test remote - _, err := post("http://localhost:"+tmpPort, "foo") + _, err = post("http://localhost:"+tmpPort, "foo") if err == nil { t.Fatal(err) }