Skip to content

Commit 2d6c5cb

Browse files
committed
Update client to fall-back to MD5 fingerprints
Signed-off-by: Simon Rüegg <[email protected]>
1 parent 7d9a171 commit 2d6c5cb

File tree

5 files changed

+108
-9
lines changed

5 files changed

+108
-9
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,8 @@ $ chisel client --help
244244
245245
Options:
246246
247-
--fingerprint, A *strongly recommended* fingerprint string
248-
to perform host-key validation against the server's public key.
249-
You may provide just a prefix of the key or the entire string.
247+
--fingerprint, A *strongly recommended* fingerprint (base64 encoded SHA256)
248+
string to perform host-key validation against the server's public key.
250249
Fingerprint mismatches will close the connection.
251250
252251
--auth, An optional username and password (client authentication)
@@ -319,7 +318,7 @@ $ chisel client --help
319318

320319
### Security
321320

322-
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.
321+
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.
323322

324323
### Authentication
325324

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

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

347346
3. Point your SOCKS5 clients (e.g. OS/Browser) to:

client/client.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package chclient
22

33
import (
44
"context"
5+
"crypto/md5"
56
"crypto/tls"
67
"crypto/x509"
8+
"encoding/base64"
79
"errors"
810
"fmt"
911
"io/ioutil"
@@ -194,15 +196,40 @@ func (c *Client) Run() error {
194196

195197
func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKey) error {
196198
expect := c.config.Fingerprint
199+
if expect == "" {
200+
return nil
201+
}
197202
got := ccrypto.FingerprintKey(key)
198-
if expect != "" && !strings.HasPrefix(got, expect) {
203+
_, err := base64.StdEncoding.DecodeString(expect)
204+
if _, ok := err.(base64.CorruptInputError); ok {
205+
c.Logger.Infof("Specified deprecated MD5 fingerprint (%s), please update to the new SHA256 fingerprint: %s", expect, got)
206+
return c.verifyLegacyFingerprint(key)
207+
} else if err != nil {
208+
return fmt.Errorf("Error decoding fingerprint: %w", err)
209+
}
210+
if got != expect {
199211
return fmt.Errorf("Invalid fingerprint (%s)", got)
200212
}
201213
//overwrite with complete fingerprint
202214
c.Infof("Fingerprint %s", got)
203215
return nil
204216
}
205217

218+
//verifyLegacyFingerprint calculates and compares legacy MD5 fingerprints
219+
func (c *Client) verifyLegacyFingerprint(key ssh.PublicKey) error {
220+
bytes := md5.Sum(key.Marshal())
221+
strbytes := make([]string, len(bytes))
222+
for i, b := range bytes {
223+
strbytes[i] = fmt.Sprintf("%02x", b)
224+
}
225+
got := strings.Join(strbytes, ":")
226+
expect := c.config.Fingerprint
227+
if !strings.HasPrefix(got, expect) {
228+
return fmt.Errorf("Invalid fingerprint (%s)", got)
229+
}
230+
return nil
231+
}
232+
206233
//Start client and does not block
207234
func (c *Client) Start(ctx context.Context) error {
208235
ctx, cancel := context.WithCancel(ctx)

client/client_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package chclient
22

33
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
46
"log"
57
"net/http"
68
"net/http/httptest"
79
"sync"
810
"testing"
911
"time"
12+
13+
"github.com/jpillora/chisel/share/ccrypto"
14+
"golang.org/x/crypto/ssh"
1015
)
1116

1217
func TestCustomHeaders(t *testing.T) {
@@ -39,3 +44,72 @@ func TestCustomHeaders(t *testing.T) {
3944
wg.Wait()
4045
c.Close()
4146
}
47+
48+
func TestFallbackLegacyFingerprint(t *testing.T) {
49+
config := Config{
50+
Fingerprint: "a5:32:92:c6:56:7a:9e:61:26:74:1b:81:a6:f5:1b:44",
51+
}
52+
c, err := NewClient(&config)
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
r := ccrypto.NewDetermRand([]byte("test123"))
57+
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
58+
if err != nil {
59+
t.Fatal(err)
60+
}
61+
pub, err := ssh.NewPublicKey(&priv.PublicKey)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
err = c.verifyServer("", nil, pub)
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
}
70+
71+
func TestVerifyLegacyFingerprint(t *testing.T) {
72+
config := Config{
73+
Fingerprint: "a5:32:92:c6:56:7a:9e:61:26:74:1b:81:a6:f5:1b:44",
74+
}
75+
c, err := NewClient(&config)
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
r := ccrypto.NewDetermRand([]byte("test123"))
80+
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
pub, err := ssh.NewPublicKey(&priv.PublicKey)
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
err = c.verifyLegacyFingerprint(pub)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
}
93+
94+
func TestVerifyFingerprint(t *testing.T) {
95+
config := Config{
96+
Fingerprint: "qmrRoo8MIqePv3jC8+wv49gU6uaFgD3FASQx9V8KdmY=",
97+
}
98+
c, err := NewClient(&config)
99+
if err != nil {
100+
t.Fatal(err)
101+
}
102+
r := ccrypto.NewDetermRand([]byte("test123"))
103+
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
104+
if err != nil {
105+
t.Fatal(err)
106+
}
107+
pub, err := ssh.NewPublicKey(&priv.PublicKey)
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
err = c.verifyServer("", nil, pub)
112+
if err != nil {
113+
t.Fatal(err)
114+
}
115+
}

main.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,8 @@ var clientHelp = `
328328
329329
Options:
330330
331-
--fingerprint, A *strongly recommended* fingerprint string
331+
--fingerprint, A *strongly recommended* fingerprint (base64 encoded SHA256) string
332332
to perform host-key validation against the server's public key.
333-
You may provide just a prefix of the key or the entire string.
334333
Fingerprint mismatches will close the connection.
335334
336335
--auth, An optional username and password (client authentication)

test/bench/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func main() {
190190

191191
hf := exec.Command("chisel", "client",
192192
// "-v",
193-
"--fingerprint", "ed:f2:cf:3c:56",
193+
"--fingerprint", "mOz4rg9zlQ409XAhhj6+fDDVwQMY42CL3Zg2W2oTYxA=",
194194
"127.0.0.1:2002",
195195
"2001:3000")
196196
hf.Stdout = os.Stdout

0 commit comments

Comments
 (0)