From 92feadbb704c36c963b7dae60ad621b5ff92ab05 Mon Sep 17 00:00:00 2001 From: Jan Tytgat Date: Mon, 13 Jan 2025 14:54:09 +0100 Subject: [PATCH] convert.go: - Add documentation - Add tests --- pkg/transcrypt/convert.go | 59 +++++-- pkg/transcrypt/convert_test.go | 291 +++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 18 deletions(-) create mode 100644 pkg/transcrypt/convert_test.go diff --git a/pkg/transcrypt/convert.go b/pkg/transcrypt/convert.go index aa0af7b..c308300 100644 --- a/pkg/transcrypt/convert.go +++ b/pkg/transcrypt/convert.go @@ -12,35 +12,58 @@ import ( "github.com/minio/sio" ) +// Defines the default layout of a string representing encrypted data. +// The string is divided in sections delimited by a colon. +// 1. Cipher suite +// 2. Salt +// 3. Data +// 4. Original data type var regexEncryptedString = regexp.MustCompile(`\d{2}:[\w\d]{24}:[\w\d]*:[\w\d]*`) +// convertBytesToValue converts a byte-array to a reflect.Value. +// It takes a byte-slice and a reflect.Kind and returns an error if the conversion fails. +func convertBytesToValue(d []byte, k reflect.Kind) (reflect.Value, error) { + switch k { + case reflect.Int: + v := reflect.New(reflect.TypeOf(0)) + v.Elem().SetInt(int64(binary.BigEndian.Uint64(d))) + return reflect.ValueOf(v.Elem().Interface()), nil + case reflect.Uint64: + v := reflect.New(reflect.TypeOf(uint64(0))) + v.Elem().SetUint(binary.BigEndian.Uint64(d)) + return reflect.ValueOf(v.Elem().Interface()), nil + case reflect.String: + return reflect.ValueOf(string(d)), nil + default: + return reflect.Value{}, fmt.Errorf("unknown type %v", k) + } +} + +// convertValueToHexString converts a value to a hex-encoded string. +// It returns an empty string and an error if the value is a kind reflect.Int and cannot be converted. func convertValueToHexString(v reflect.Value) (string, error) { var err error switch v.Kind() { case reflect.Int: - buf := make([]byte, 0) - bufWriter := bytes.NewBuffer(buf) - err = binary.Write(bufWriter, binary.BigEndian, v.Int()) - if err != nil { + bufWriter := bytes.NewBuffer(make([]byte, 0)) + if err = binary.Write(bufWriter, binary.BigEndian, v.Int()); err != nil { return "", err } return hex.EncodeToString(bufWriter.Bytes()), nil - default: + case reflect.String: return hex.EncodeToString([]byte(v.String())), nil - } -} - -func convertBytesToValue(d []byte, k reflect.Kind) (reflect.Value, error) { - switch k { - case reflect.Int: - decodedInt := binary.BigEndian.Uint64(d) - return reflect.ValueOf(int(decodedInt)), nil default: - return reflect.ValueOf(string(d)), nil + return "", fmt.Errorf("unknown type %v", v.Kind()) } } +// decodeHexString decodes data into the pieces that make up the encrypted data. +// It takes an encrypted key and data string and returns the actual encrypted data as a byte-slice, reflect.Kind and the encryption config. +// It returns an error if the data string is empty or invalid, or any of the steps to get the encrypted data fails. func decodeHexString(key string, data string) ([]byte, reflect.Kind, sio.Config, error) { + if key == "" { + return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("key is empty") + } if data == "" { return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("value is empty") } @@ -58,9 +81,9 @@ func decodeHexString(key string, data string) ([]byte, reflect.Kind, sio.Config, return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode cipersuite: %w", err) } - var nonce []byte - if nonce, err = hex.DecodeString(split[1]); err != nil { - return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode nonce: %w", err) + var salt []byte + if salt, err = hex.DecodeString(split[1]); err != nil { + return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode salt: %w", err) } var encryptedBytes []byte @@ -79,7 +102,7 @@ func decodeHexString(key string, data string) ([]byte, reflect.Kind, sio.Config, } var cryptoConfig sio.Config - if cryptoConfig, err = createCryptoConfig(key, cipherSuiteBytes, nonce); err != nil { + if cryptoConfig, err = createCryptoConfig(key, cipherSuiteBytes, salt); err != nil { return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot create crypto config: %w", err) } diff --git a/pkg/transcrypt/convert_test.go b/pkg/transcrypt/convert_test.go new file mode 100644 index 0000000..da21d6b --- /dev/null +++ b/pkg/transcrypt/convert_test.go @@ -0,0 +1,291 @@ +package transcrypt + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "reflect" + "testing" + + "github.com/minio/sio" +) + +func Test_convertBytesToValue_String(t *testing.T) { + type args struct { + d []byte + k reflect.Kind + } + + tests := []struct { + name string + args args + want reflect.Value + wantErr bool + }{ + { + name: "string", + args: args{ + d: []byte("hello world"), + k: reflect.TypeOf("hello world").Kind(), + }, + want: reflect.ValueOf("hello world"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertBytesToValue(tt.args.d, tt.args.k) + if (err != nil) != tt.wantErr { + t.Errorf("convertBytesToValue() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got.String(), tt.want.String()) { + t.Errorf("convertBytesToValue() got = %v, want = %v", got, tt.want) + } + }) + } +} + +func Test_convertBytesToValue_Uint(t *testing.T) { + type args struct { + d []byte + k reflect.Kind + } + var inputUint uint64 = 132130 + bufWriterUint := bytes.NewBuffer(make([]byte, 0)) + if bufErr := binary.Write(bufWriterUint, binary.BigEndian, inputUint); bufErr != nil { + panic(bufErr) + } + + var inputInt = 132130 + bufWriterInt := bytes.NewBuffer(make([]byte, 0)) + if bufErr := binary.Write(bufWriterInt, binary.BigEndian, int64(inputInt)); bufErr != nil { + panic(bufErr) + } + tests := []struct { + name string + args args + want reflect.Value + wantErr bool + }{ + { + name: "uint64", + args: args{ + d: bufWriterUint.Bytes(), + k: reflect.TypeOf(inputUint).Kind(), + }, + want: reflect.ValueOf(inputUint), + wantErr: false, + }, + { + name: "int64", + args: args{ + d: bufWriterInt.Bytes(), + k: reflect.TypeOf(inputInt).Kind(), + }, + want: reflect.ValueOf(inputInt), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertBytesToValue(tt.args.d, tt.args.k) + if (err != nil) != tt.wantErr { + t.Errorf("convertBytesToValue() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got == tt.want { + t.Errorf("convertBytesToValue() got = %v, want = %v", got, tt.want) + } + }) + } +} + +func Test_convertValueToHexString(t *testing.T) { + type args struct { + v reflect.Value + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "string", + args: args{ + v: reflect.ValueOf("hello world"), + }, + want: "68656c6c6f20776f726c64", + wantErr: false, + }, + { + name: "int", + args: args{ + v: reflect.ValueOf(132130), + }, + want: "0000000000020422", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertValueToHexString(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("convertValueToHexString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("convertValueToHexString() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_decodeHexString(t *testing.T) { + type args struct { + key string + data string + } + + decodedHexKey, _ := hex.DecodeString("308204a20201000282010100b44756063cdb25f2e5ec868ecc5edc733896d637f5e0ffd8a3f820fb0f2acb2268c69a7774c2d4c4ede1ec47d894bbb0f928e9e700db03767b6152548c49f3170d011dd87aa3d36a1f49d38756bcbc6e122d362ff3986165985d264f11ba8e1d35006b66290c4efa8dfd6b4bcbb5e3fbf9277d31ffaa09b319a976313c3bcd25f3b74be78df293bd7b38e5a6caf48be79684882af47630eb147ecb4426e9e7e6e9554b1d8b53530c5c22cfe44e95bfeeca4bb79f1a3c08e5de93d3bf458aae5e821e9e6c4ec6c3602b9b1f56b31da14775608374ec8653e34e2025901cbef725241b166187972dc1c1913f2c8ba54f1ceb443b4019096fac816cf6334aa2e3f50203010001028201004703b2c75241a17945491ed831794cadb6a4f44da6f5b2d2cc047a396b8817ecbe093ddfc086def9941b62d00a68cc66b23f83a4139a328b019f1ca7617bdfde3ca92bf0929ee630ef924d590ab9de201dd8e17792257c7860c490caa4d930122146c107c533ac08d6d5f4e62ea0bfe60a079c318ddc95658fbe4968aba982edbe0775e71eba35836758b2c486e54e54de4cbb3b7004a9e16b0d0da3b88dbf026413d97e0396aec1f739d504b4eb75a7719fb7bda9886c78a99a79a55ad2b06c36a46ac6df71cd021296020865a2f79b3a0950f9268a1fc773ab407e3e283c10f6fe9412acbae260c2a5c601d75a731db18294a91d20cce7d73d039d7162742102818100efad47d757feb7f570f55f81b7b0fe271de91633234146b5f9fb682b3aafd574481bbd49c85b27c4c118f2634755800b4eb967e46ff6ca4f392bac226410ce31b7c0d22735e61928a940d36033aac6aaa088d21a36720b90904a7ea8b62f998a99938d5ced7b8f2eb2339e34559b25506c43bed054562543a260f426c3136ac702818100c08e75852d7375a09087a5e793d7372e3804257dda911408a1270a14a825879e26d5c7b85796a5ff6f4a290a9185cc097d3eebe0bb2fd8520b50cc9f4bb30a7815a51a23d5ea752f8035c242bce5839ad4f4c6fcb5921e1a8a8f672fc378a7afc57a04c2e882c21414c656b84bf8c3efd4ea0dbbd4269e048e66196d57b99f630281807f592ce0e8ea78c83afac582611df40cc8c1be7ff16d8faac566a5d4c25c0728bfdfad55f4d52a6e4ac37c96efa22864d9b17dd84cfd6e4565f5248329741c7b224d9bdc25b15b10d5cd92027db171d9db6e976442259aab775f7da91b14739ac73b35537903bbf26dd12b7057441631833503c021ef9be131f81e023288b0a50281807cc552bd4320479e0d48f865c0547a3b06ad19261dd45828e75386a2aff9f190b7155b5ec5d2a629881183da87452d5b10bf0ed506361073c94547f20879315572a112f91989dcf93498a111e198ced82b19993ef2e08585293796e34a440a54491fb1aa22436842dedb4e2209885e5e2f96a1e38daaa045cf87b4fe3713de8502818021680ea1a93b048af3deec6fd09d9d110d391e908cb6eeab615e5595556f07238c44f7b8795cffe3508aa3dff21de927576454537473e9b07eaedf0864f4a5a0f6275fbef3f6c9b42f7c692b351451004c25c4918edabbd1f22e5d174aac550db7327e99cb58be7641ec0479425bf00f8b3640685a95e70c93e820c7f39e1a1e") + tests := []struct { + name string + args args + wantData []byte + wantKind reflect.Kind + wantConfig sio.Config + wantErr bool + }{ + { + name: "empty_key", + args: args{ + key: "", + data: "", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "empty_data", + args: args{ + key: string(decodedHexKey), + data: "", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "invalid_value_ciphersuite", + args: args{ + key: string(decodedHexKey), + data: "__:68e191dfc1f3180904d19a58:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:737472696e67", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "invalid_value_nonce", + args: args{ + key: string(decodedHexKey), + data: "00:__:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:737472696e67", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "invalid_value_data", + args: args{ + key: string(decodedHexKey), + data: "00:68e191dfc1f3180904d19a58:__:737472696e67", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "invalid_value_kind", + args: args{ + key: string(decodedHexKey), + data: "00:68e191dfc1f3180904d19a58:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:__", + }, + wantData: nil, + wantKind: reflect.Invalid, + wantErr: true, + }, + { + name: "invalid_ciphersuite", + args: args{ + key: string(decodedHexKey), + data: "dd:68e191dfc1f3180904d19a58:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:737472696e67", + }, + wantData: nil, + wantKind: reflect.String, + wantErr: true, + }, + { + name: "invalid_nonce", + args: args{ + key: string(decodedHexKey), + data: "00:dddddddddddddddddddddddd:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:737472696e67", + }, + wantData: nil, + wantKind: reflect.String, + wantErr: true, + }, + { + name: "invalid_data", + args: args{ + key: string(decodedHexKey), + data: "00:68e191dfc1f3180904d19a58:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:737472696e67", + }, + wantData: nil, + wantKind: reflect.String, + wantErr: true, + }, + { + name: "invalid_kind", + args: args{ + key: string(decodedHexKey), + data: "00:68e191dfc1f3180904d19a58:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:dddddddddddd", + }, + wantData: nil, + wantKind: reflect.String, + wantErr: true, + }, + { + name: "valid", + args: args{ + key: string(decodedHexKey), + data: "00:68e191dfc1f3180904d19a58:20001500e8e191dfc1f3180904d19a589d6c41d057473145672f5e7a90b1fa1d47b21ece952eafbbfa38668f2885b323179721bc10a5:737472696e67", + }, + wantData: nil, + wantKind: reflect.String, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotData, gotKind, gotConfig, err := decodeHexString(tt.args.key, tt.args.data) + if err != nil { + if (err != nil) != tt.wantErr { + t.Errorf("decodeHexString() error = %v, wantErr %v", err, tt.wantErr) + } + return + } + if string(gotData) == string(tt.wantData) { + t.Errorf("decodeHexString() gotData = %v, wantData %v", gotData, tt.wantData) + } + if gotKind != tt.wantKind { + t.Errorf("decodeHexString() gotKind = %v, wantKind %v", gotKind, tt.wantKind) + } + if string(gotConfig.CipherSuites) == string(tt.wantConfig.CipherSuites) { + t.Errorf("decodeHexString() gotCipherSuites = %v, wantCipherSuites %v", gotConfig, tt.wantConfig) + } + }) + } +}