Skip to content

Commit

Permalink
Update client to fall-back to MD5 fingerprints
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Rüegg <simon@rueggs.ch>
  • Loading branch information
srueg committed Nov 10, 2020
1 parent 7d9a171 commit 129811e
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 9 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,8 @@ $ chisel client --help
Options:
--fingerprint, A *strongly recommended* fingerprint string
to perform host-key validation against the server's public key.
You may provide just a prefix of the key or the entire string.
--fingerprint, A *strongly recommended* fingerprint (base64 encoded SHA256)
string to perform host-key validation against the server's public key.
Fingerprint mismatches will close the connection.
--auth, An optional username and password (client authentication)
Expand Down Expand Up @@ -319,7 +318,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 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 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.

### Authentication

Expand All @@ -341,7 +340,7 @@ docker run \
2. Connect your chisel client (using server's fingerprint)

```sh
chisel client --fingerprint ab:12:34 <server-address>:9312 socks
chisel client --fingerprint 'rHb55mcxf6vSckL2AezFV09rLs7pfPpavVu++MF7AhQ=' <server-address>:9312 socks
```

3. Point your SOCKS5 clients (e.g. OS/Browser) to:
Expand Down
29 changes: 28 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package chclient

import (
"context"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -194,15 +196,40 @@ func (c *Client) Run() error {

func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKey) error {
expect := c.config.Fingerprint
if expect == "" {
return nil
}
got := ccrypto.FingerprintKey(key)
if expect != "" && !strings.HasPrefix(got, expect) {
_, err := base64.StdEncoding.DecodeString(expect)
if _, ok := err.(base64.CorruptInputError); ok {
c.Logger.Infof("Specified deprecated MD5 fingerprint (%s), please update to the new SHA256 fingerprint: %s", expect, got)
return c.verifyLegacyFingerprint(key)
} else if err != nil {
return fmt.Errorf("error decoding fingerprint: %w", err)
}
if got != expect {
return fmt.Errorf("Invalid fingerprint (%s)", got)
}
//overwrite with complete fingerprint
c.Infof("Fingerprint %s", got)
return nil
}

//verifyLegacyFingerprint calculates and compares legacy MD5 fingerprints
func (c *Client) verifyLegacyFingerprint(key ssh.PublicKey) error {
bytes := md5.Sum(key.Marshal())
strbytes := make([]string, len(bytes))
for i, b := range bytes {
strbytes[i] = fmt.Sprintf("%02x", b)
}
got := strings.Join(strbytes, ":")
expect := c.config.Fingerprint
if !strings.HasPrefix(got, expect) {
return fmt.Errorf("Invalid fingerprint (%s)", got)
}
return nil
}

//Start client and does not block
func (c *Client) Start(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
Expand Down
74 changes: 74 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package chclient

import (
"crypto/ecdsa"
"crypto/elliptic"
"log"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"

"github.com/jpillora/chisel/share/ccrypto"
"golang.org/x/crypto/ssh"
)

func TestCustomHeaders(t *testing.T) {
Expand Down Expand Up @@ -39,3 +44,72 @@ func TestCustomHeaders(t *testing.T) {
wg.Wait()
c.Close()
}

func TestFallbackLegacyFingerprint(t *testing.T) {
config := Config{
Fingerprint: "a5:32:92:c6:56:7a:9e:61:26:74:1b:81:a6:f5:1b:44",
}
c, err := NewClient(&config)
if err != nil {
t.Fatal(err)
}
r := ccrypto.NewDetermRand([]byte("test123"))
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
if err != nil {
t.Fatal(err)
}
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
t.Fatal(err)
}
err = c.verifyServer("", nil, pub)
if err != nil {
t.Fatal(err)
}
}

func TestVerifyLegacyFingerprint(t *testing.T) {
config := Config{
Fingerprint: "a5:32:92:c6:56:7a:9e:61:26:74:1b:81:a6:f5:1b:44",
}
c, err := NewClient(&config)
if err != nil {
t.Fatal(err)
}
r := ccrypto.NewDetermRand([]byte("test123"))
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
if err != nil {
t.Fatal(err)
}
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
t.Fatal(err)
}
err = c.verifyLegacyFingerprint(pub)
if err != nil {
t.Fatal(err)
}
}

func TestVerifyFingerprint(t *testing.T) {
config := Config{
Fingerprint: "qmrRoo8MIqePv3jC8+wv49gU6uaFgD3FASQx9V8KdmY=",
}
c, err := NewClient(&config)
if err != nil {
t.Fatal(err)
}
r := ccrypto.NewDetermRand([]byte("test123"))
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
if err != nil {
t.Fatal(err)
}
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
t.Fatal(err)
}
err = c.verifyServer("", nil, pub)
if err != nil {
t.Fatal(err)
}
}
3 changes: 1 addition & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,8 @@ var clientHelp = `
Options:
--fingerprint, A *strongly recommended* fingerprint string
--fingerprint, A *strongly recommended* fingerprint (base64 encoded SHA256) string
to perform host-key validation against the server's public key.
You may provide just a prefix of the key or the entire string.
Fingerprint mismatches will close the connection.
--auth, An optional username and password (client authentication)
Expand Down
2 changes: 1 addition & 1 deletion test/bench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func main() {

hf := exec.Command("chisel", "client",
// "-v",
"--fingerprint", "ed:f2:cf:3c:56",
"--fingerprint", "mOz4rg9zlQ409XAhhj6+fDDVwQMY42CL3Zg2W2oTYxA=",
"127.0.0.1:2002",
"2001:3000")
hf.Stdout = os.Stdout
Expand Down

0 comments on commit 129811e

Please sign in to comment.