From b1b2572d5bd9d991adf0b326496e6c823ef74183 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Fri, 23 Oct 2020 22:23:58 -0400 Subject: [PATCH] Fix key derivation --- shadowsocks/cipher.go | 18 ++++++++- shadowsocks/compatibility_test.go | 65 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 shadowsocks/compatibility_test.go diff --git a/shadowsocks/cipher.go b/shadowsocks/cipher.go index be97bbb6..837770b3 100644 --- a/shadowsocks/cipher.go +++ b/shadowsocks/cipher.go @@ -17,6 +17,7 @@ package shadowsocks import ( "crypto/aes" "crypto/cipher" + "crypto/md5" "crypto/sha1" "fmt" "io" @@ -115,13 +116,28 @@ func (c *Cipher) NewAEAD(salt []byte) (cipher.AEAD, error) { return c.aead.newInstance(sessionKey) } +// Function definition at https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html +func simpleEVPBytesToKey(data []byte, keyLen int) []byte { + var derived, di []byte + h := md5.New() + for len(derived) < keyLen { + h.Write(di) + h.Write(data) + derived = h.Sum(derived) + di = derived[len(derived)-h.Size():] + h.Reset() + } + return derived[:keyLen] +} + // NewCipher creates a Cipher given a cipher name and a secret func NewCipher(cipherName string, secretText string) (*Cipher, error) { - secret := []byte(secretText) aeadSpec, err := getAEADSpec(cipherName) if err != nil { return nil, err } + // Key derivation as per https://shadowsocks.org/en/spec/AEAD-Ciphers.html + secret := simpleEVPBytesToKey([]byte(secretText), aeadSpec.keySize) return &Cipher{*aeadSpec, secret}, nil } diff --git a/shadowsocks/compatibility_test.go b/shadowsocks/compatibility_test.go new file mode 100644 index 00000000..df38a0c2 --- /dev/null +++ b/shadowsocks/compatibility_test.go @@ -0,0 +1,65 @@ +// Copyright 2020 Jigsaw Operations 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 +// +// https://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. + +package shadowsocks + +import ( + "io" + "net" + "sync" + "testing" + + "github.com/shadowsocks/go-shadowsocks2/core" + "github.com/shadowsocks/go-shadowsocks2/shadowaead" + "github.com/stretchr/testify/require" +) + +func TestCompatibility(t *testing.T) { + cipherName := "chacha20-ietf-poly1305" + secret := "secret" + toRight := "payload1" + fromRight := "payload2" + left, right := net.Pipe() + + var wait sync.WaitGroup + wait.Add(1) + go func() { + cipher, err := NewCipher(cipherName, secret) + require.Nil(t, err, "NewCipher failed: %v", err) + ssWriter := NewShadowsocksWriter(left, cipher) + ssWriter.Write([]byte(toRight)) + + ssReader := NewShadowsocksReader(left, cipher) + output := make([]byte, len(fromRight)) + _, err = ssReader.Read(output) + require.Nil(t, err, "Read failed: %v", err) + require.Equal(t, fromRight, string(output)) + left.Close() + wait.Done() + }() + + otherCipher, err := core.PickCipher(cipherName, []byte{}, secret) + require.Nil(t, err) + conn := shadowaead.NewConn(right, otherCipher.(shadowaead.Cipher)) + output := make([]byte, len(toRight)) + _, err = io.ReadFull(conn, output) + require.Nil(t, err) + require.Equal(t, toRight, string(output)) + + _, err = conn.Write([]byte(fromRight)) + require.Nil(t, err, "Write failed: %v", err) + + conn.Close() + wait.Wait() +}