mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-02 07:26:53 +00:00
build/ci: Update Go to v1.24, golangci-lint to v1.64.6 and fix issues (#1804)
* build/ci: Update Go to v1.24, golangci-lint to v1.64.5 and fix issues * Address shazbert's nitters * linter/config: Fix new linter issue and use versionSize const * Address gk's nitters and fix additional linter issue after rebase * Address glorious nits * staticcheck: Fix additional linter issues after upgrading to Go 1.24.1 and golangci-lint v1.64.6 Also addresses nits * Improve testing, assertify usage and use common.ErrParsingWSField * TestCreateNewStrategy: Replace must > should wording
This commit is contained in:
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
saltRandomLength = 12
|
||||
saltRandomLength = 12
|
||||
encryptionVersion = 1
|
||||
versionSize = 2 // 2 bytes as uint16, allows for 65535 versions (at our current rate of 1 version per decade, should last a few generations)
|
||||
)
|
||||
|
||||
// Public errors
|
||||
@@ -27,14 +29,16 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
errAESBlockSize = errors.New("config file data is too small for the AES required block size")
|
||||
errNoPrefix = errors.New("data does not start with Encryption Prefix")
|
||||
errKeyIsEmpty = errors.New("key is empty")
|
||||
errUserInput = errors.New("error getting user input")
|
||||
errAESBlockSize = errors.New("config file data is too small for the AES required block size")
|
||||
errNoPrefix = errors.New("data does not start with Encryption Prefix")
|
||||
errKeyIsEmpty = errors.New("key is empty")
|
||||
errUserInput = errors.New("error getting user input")
|
||||
errUnsupportedEncryptionVersion = errors.New("unsupported encryption version")
|
||||
|
||||
// encryptionPrefix is a prefix to tell us the file is encrypted
|
||||
encryptionPrefix = []byte("THORS-HAMMER")
|
||||
saltPrefix = []byte("~GCT~SO~SALTY~")
|
||||
encryptionPrefix = []byte("THORS-HAMMER")
|
||||
saltPrefix = []byte("~GCT~SO~SALTY~")
|
||||
encryptionVersionPrefix = []byte("ENCVER")
|
||||
)
|
||||
|
||||
// promptForConfigEncryption asks for encryption confirmation
|
||||
@@ -120,18 +124,24 @@ func (c *Config) encryptConfigData(configData []byte) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, aes.BlockSize+len(configData))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
aead, err := cipher.NewGCMWithRandomNonce(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext[aes.BlockSize:], configData)
|
||||
ciphertext := aead.Seal(nil, nil, configData, nil)
|
||||
|
||||
appendedFile := append(bytes.Clone(encryptionPrefix), c.storedSalt...)
|
||||
appendedFile = append(appendedFile, ciphertext...)
|
||||
appendedFile := make([]byte, len(encryptionPrefix)+len(c.storedSalt)+len(encryptionVersionPrefix)+versionSize+len(ciphertext))
|
||||
offset := 0
|
||||
copy(appendedFile[offset:], encryptionPrefix)
|
||||
offset += len(encryptionPrefix)
|
||||
copy(appendedFile[offset:], c.storedSalt)
|
||||
offset += len(c.storedSalt)
|
||||
copy(appendedFile[offset:], encryptionVersionPrefix)
|
||||
offset += len(encryptionVersionPrefix)
|
||||
binary.BigEndian.PutUint16(appendedFile[offset:offset+versionSize], encryptionVersion)
|
||||
offset += versionSize
|
||||
copy(appendedFile[offset:], ciphertext)
|
||||
return appendedFile, nil
|
||||
}
|
||||
|
||||
@@ -165,23 +175,63 @@ func (c *Config) decryptConfigData(d, key []byte) ([]byte, error) {
|
||||
d = d[len(salt):]
|
||||
}
|
||||
|
||||
blockDecrypt, err := aes.NewCipher(key)
|
||||
var ciphertext []byte
|
||||
if !bytes.HasPrefix(d, encryptionVersionPrefix) {
|
||||
ciphertext, err = decryptAESCFBCiphertext(d, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
d = d[len(encryptionVersionPrefix):]
|
||||
switch ver := binary.BigEndian.Uint16(d[:versionSize]); ver {
|
||||
case 1: // TODO: Intertwine this with the existing config versioning system
|
||||
d = d[versionSize:]
|
||||
ciphertext, err = decryptAESGCMCiphertext(d, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %d", errUnsupportedEncryptionVersion, ver)
|
||||
}
|
||||
}
|
||||
|
||||
c.sessionDK, c.storedSalt = sessionDK, storedSalt
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// decryptAESGCMCiphertext decrypts the ciphertext using AES-GCM
|
||||
func decryptAESGCMCiphertext(data, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(d) < aes.BlockSize {
|
||||
cipherAEAD, err := cipher.NewGCMWithRandomNonce(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cipherAEAD.Open(nil, nil, data, nil)
|
||||
}
|
||||
|
||||
// decryptAESCFBCiphertext decrypts the ciphertext using AES-CFB (legacy mode)
|
||||
func decryptAESCFBCiphertext(data, key []byte) ([]byte, error) {
|
||||
if len(data) < aes.BlockSize {
|
||||
return nil, errAESBlockSize
|
||||
}
|
||||
|
||||
iv, d := d[:aes.BlockSize], d[aes.BlockSize:]
|
||||
iv := data[:aes.BlockSize]
|
||||
ciphertext := data[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(blockDecrypt, iv)
|
||||
stream.XORKeyStream(d, d)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.sessionDK, c.storedSalt = sessionDK, storedSalt
|
||||
|
||||
return d, nil
|
||||
stream := cipher.NewCFBDecrypter(block, iv) //nolint:staticcheck // Deprecated CFB is used for legacy mode
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// IsEncrypted returns if the data sequence is encrypted
|
||||
|
||||
@@ -3,9 +3,13 @@ package config
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -57,7 +61,7 @@ func TestPromptForConfigKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncryptConfigFile(t *testing.T) {
|
||||
func TestEncryptConfigData(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := EncryptConfigData([]byte("test"), nil)
|
||||
require.ErrorIs(t, err, errKeyIsEmpty)
|
||||
@@ -83,7 +87,7 @@ func TestEncryptConfigFile(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDecryptConfigFile(t *testing.T) {
|
||||
func TestDecryptConfigData(t *testing.T) {
|
||||
t.Parallel()
|
||||
e, err := EncryptConfigData([]byte(`{"test":1}`), []byte("key"))
|
||||
require.NoError(t, err)
|
||||
@@ -100,6 +104,93 @@ func TestDecryptConfigFile(t *testing.T) {
|
||||
|
||||
_, err = DecryptConfigData(encryptionPrefix, []byte("AAAAAAAAAAAAAAAA"))
|
||||
require.ErrorIs(t, err, errAESBlockSize)
|
||||
|
||||
sessionDK, salt, err := makeNewSessionDK([]byte("key"))
|
||||
require.NoError(t, err, "makeNewSessionDK must not error")
|
||||
|
||||
encData, err := legacyEncrypt(t, salt, []byte(`{"test":123}`), sessionDK)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := DecryptConfigData(encData, []byte("key"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `{"test":123}`, string(data))
|
||||
|
||||
badVersion := make([]byte, len(encryptionPrefix)+len(encryptionVersionPrefix)+versionSize)
|
||||
copy(badVersion, encryptionPrefix)
|
||||
copy(badVersion[len(encryptionPrefix):], encryptionVersionPrefix)
|
||||
binary.BigEndian.PutUint16(badVersion[len(encryptionPrefix)+len(encryptionVersionPrefix):], 69)
|
||||
_, err = DecryptConfigData(badVersion, []byte("key"))
|
||||
require.ErrorIs(t, err, errUnsupportedEncryptionVersion)
|
||||
}
|
||||
|
||||
func legacyEncrypt(t *testing.T, salt, data, key []byte) ([]byte, error) {
|
||||
t.Helper()
|
||||
|
||||
ciphertext, err := aesCFBEncrypt(t, data, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encData := append(bytes.Clone(encryptionPrefix), salt...)
|
||||
encData = append(encData, ciphertext...)
|
||||
return encData, nil
|
||||
}
|
||||
|
||||
func aesCFBEncrypt(t *testing.T, data, key []byte) ([]byte, error) {
|
||||
t.Helper()
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, aes.BlockSize+len(data))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv) //nolint:staticcheck // For testing purposes only
|
||||
stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func TestEncryptAESGCMCiphertext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := decryptAESGCMCiphertext(nil, nil)
|
||||
require.ErrorIs(t, err, aes.KeySizeError(0))
|
||||
|
||||
validKey := []byte(strings.Repeat("A", 16))
|
||||
block, err := aes.NewCipher(validKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
aead, err := cipher.NewGCMWithRandomNonce(block)
|
||||
require.NoError(t, err)
|
||||
|
||||
ciphertext := aead.Seal(nil, nil, []byte("MEOWMEOWMEOWMEOWMEOW"), nil)
|
||||
|
||||
data, err := decryptAESGCMCiphertext(ciphertext, validKey)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "MEOWMEOWMEOWMEOWMEOW", string(data))
|
||||
}
|
||||
|
||||
func TestDecryptAESCFBCiphertext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := decryptAESCFBCiphertext(nil, nil)
|
||||
require.ErrorIs(t, err, errAESBlockSize)
|
||||
|
||||
_, err = decryptAESCFBCiphertext([]byte("WOOFWOOFWOOFWOOFWOOF"), []byte("A"))
|
||||
require.ErrorIs(t, err, aes.KeySizeError(1))
|
||||
|
||||
validKey := []byte(strings.Repeat("A", 16))
|
||||
data, err := aesCFBEncrypt(t, []byte("WOOFWOOFWOOFWOOFWOOF"), validKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err = decryptAESCFBCiphertext(data, validKey)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "WOOFWOOFWOOFWOOFWOOF", string(data))
|
||||
}
|
||||
|
||||
func TestIsEncrypted(t *testing.T) {
|
||||
|
||||
@@ -1746,7 +1746,7 @@ func BenchmarkUpdateConfig(b *testing.B) {
|
||||
}
|
||||
|
||||
newCfg := c
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
_ = c.UpdateConfig(TestFile, &newCfg, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ func (m *manager) latest() (uint16, error) {
|
||||
if len(m.versions) == 0 {
|
||||
return 0, errNoVersions
|
||||
}
|
||||
return uint16(len(m.versions)) - 1, nil
|
||||
return uint16(len(m.versions)) - 1, nil //nolint:gosec // Ignore this as we hardcode version numbers
|
||||
}
|
||||
|
||||
// checkVersions ensures that registered versions are consistent
|
||||
|
||||
Reference in New Issue
Block a user