feat(tfstated): implement GET and POST methods, states are encrypted in a sqlite3 database

This commit is contained in:
Julien Dessaux 2024-09-30 00:58:49 +02:00
parent baf5aac08e
commit 4ff490806c
Signed by: adyxax
GPG key ID: F92E51B86E07177E
18 changed files with 627 additions and 2 deletions

13
pkg/scrypto/LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright (c) 2023-2024 Nicolas Martyanoff <nicolas@n16f.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

5
pkg/scrypto/README.md Normal file
View file

@ -0,0 +1,5 @@
Original code imported from https://github.com/galdor/go-service/tree/master/pkg/scrypto
Alterations:
- converted the EncryptAES256 and DecryptAES256 functions to methods
- rewrote the tests to get rid of the dependency on testify

138
pkg/scrypto/aes256.go Normal file
View file

@ -0,0 +1,138 @@
package scrypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
)
type AES256Key [32]byte
const (
AES256IVSize int = aes.BlockSize
)
var Zero = AES256Key{}
func (key *AES256Key) Bytes() []byte {
return key[:]
}
func (key *AES256Key) IsZero() bool {
return bytes.Equal(key.Bytes(), Zero.Bytes())
}
func (key *AES256Key) Hex() string {
return hex.EncodeToString(key[:])
}
func (key *AES256Key) FromHex(s string) error {
data, err := hex.DecodeString(s)
if err != nil {
return err
}
if len(data) != 32 {
return fmt.Errorf("invalid key size")
}
copy((*key)[:], data[:32])
return nil
}
func (key *AES256Key) FromBase64(s string) error {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return err
}
if len(data) != 32 {
return fmt.Errorf("invalid key size")
}
copy((*key)[:], data[:32])
return nil
}
func (key *AES256Key) MarshalJSON() ([]byte, error) {
s := base64.StdEncoding.EncodeToString(key[:])
return json.Marshal(s)
}
func (pkey *AES256Key) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
key, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return fmt.Errorf("cannot decode base64 value: %w", err)
}
if len(key) != 32 {
return fmt.Errorf("invalid key size (must be 32 bytes long)")
}
copy(pkey[:], key)
return nil
}
func (key *AES256Key) EncryptAES256(inputData []byte) ([]byte, error) {
blockCipher, err := aes.NewCipher(key[:])
if err != nil {
return nil, fmt.Errorf("cannot create cipher: %w", err)
}
paddedData := PadPKCS5(inputData, aes.BlockSize)
outputData := make([]byte, AES256IVSize+len(paddedData))
iv := outputData[:AES256IVSize]
encryptedData := outputData[AES256IVSize:]
if _, err := rand.Read(iv); err != nil {
return nil, fmt.Errorf("cannot generate iv: %w", err)
}
encrypter := cipher.NewCBCEncrypter(blockCipher, iv)
encrypter.CryptBlocks(encryptedData, paddedData)
return outputData, nil
}
func (key *AES256Key) DecryptAES256(inputData []byte) ([]byte, error) {
blockCipher, err := aes.NewCipher(key[:])
if err != nil {
return nil, fmt.Errorf("cannot create cipher: %w", err)
}
if len(inputData) < AES256IVSize {
return nil, fmt.Errorf("truncated data")
}
iv := inputData[:AES256IVSize]
paddedData := inputData[AES256IVSize:]
if len(paddedData)%aes.BlockSize != 0 {
return nil, fmt.Errorf("invalid padded data length")
}
decrypter := cipher.NewCBCDecrypter(blockCipher, iv)
decrypter.CryptBlocks(paddedData, paddedData)
outputData, err := UnpadPKCS5(paddedData, aes.BlockSize)
if err != nil {
return nil, fmt.Errorf("invalid padded data: %w", err)
}
return outputData, nil
}

View file

@ -0,0 +1,60 @@
package scrypto
import (
"encoding/hex"
"slices"
"strings"
"testing"
)
func TestAES256KeyHex(t *testing.T) {
testKeyHex := "28278b7c0a25f01d3cab639633b9487f9ea1e9a2176dc9595a3f01323aa44284"
testKey, _ := hex.DecodeString(testKeyHex)
var key AES256Key
if err := key.FromHex(testKeyHex); err != nil {
t.Errorf("got unexpected error %+v", err)
}
if slices.Compare(testKey, key[:]) != 0 {
t.Errorf("got %v, wanted %v", testKey, key[:])
}
if strings.Compare(testKeyHex, key.Hex()) != 0 {
t.Errorf("got %v, wanted %v", testKeyHex, key.Hex())
}
}
func TestAES256(t *testing.T) {
keyHex := "28278b7c0a25f01d3cab639633b9487f9ea1e9a2176dc9595a3f01323aa44284"
var key AES256Key
if err := key.FromHex(keyHex); err != nil {
t.Errorf("got unexpected error %+v", err)
}
data := []byte("Hello world!")
encryptedData, err := key.EncryptAES256(data)
if err != nil {
t.Errorf("got unexpected error when encrypting data %+v", err)
}
decryptedData, err := key.DecryptAES256(encryptedData)
if err != nil {
t.Errorf("got unexpected error when decrypting data %+v", err)
}
if slices.Compare(data, decryptedData) != 0 {
t.Errorf("got %v, wanted %v", decryptedData, data)
}
}
func TestAES256InvalidData(t *testing.T) {
keyHex := "28278b7c0a25f01d3cab639633b9487f9ea1e9a2176dc9595a3f01323aa44284"
var key AES256Key
if err := key.FromHex(keyHex); err != nil {
t.Errorf("got unexpected error when converting data from base64: %+v", err)
}
iv := make([]byte, AES256IVSize)
if _, err := key.DecryptAES256(append(iv, []byte("foo")...)); err == nil {
t.Error("decrypting operation should have failed")
}
}

31
pkg/scrypto/pkcs5.go Normal file
View file

@ -0,0 +1,31 @@
package scrypto
import (
"bytes"
"fmt"
)
func PadPKCS5(data []byte, blockSize int) []byte {
paddingSize := blockSize - len(data)%blockSize
padding := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)
return append(data, padding...)
}
func UnpadPKCS5(data []byte, blockSize int) ([]byte, error) {
dataSize := len(data)
if dataSize%blockSize != 0 {
return nil, fmt.Errorf("truncated data")
}
if len(data) == 0 {
return data, nil
} else {
paddingSize := int(data[dataSize-1])
if paddingSize > dataSize || paddingSize > blockSize {
return nil, fmt.Errorf("invalid padding size %d", paddingSize)
}
return data[:dataSize-paddingSize], nil
}
}

52
pkg/scrypto/pkcs5_test.go Normal file
View file

@ -0,0 +1,52 @@
package scrypto
import (
"slices"
"testing"
)
func TestPadPKCS5(t *testing.T) {
tests := []struct {
data []byte
want []byte
}{
{data: []byte(""), want: []byte("\x04\x04\x04\x04")},
{data: []byte("a"), want: []byte("a\x03\x03\x03")},
{data: []byte("ab"), want: []byte("ab\x02\x02")},
{data: []byte("abc"), want: []byte("abc\x01")},
{data: []byte("abcd"), want: []byte("abcd\x04\x04\x04\x04")},
{data: []byte("abcde"), want: []byte("abcde\x03\x03\x03")},
{data: []byte("abcdefgh"), want: []byte("abcdefgh\x04\x04\x04\x04")},
}
for _, tt := range tests {
got := PadPKCS5(tt.data, 4)
if slices.Compare(got, tt.want) != 0 {
t.Errorf("got %v, want %v", got, tt.want)
}
}
}
func TestUnpadPKCS5(t *testing.T) {
tests := []struct {
data []byte
want []byte
}{
{data: []byte("\x04\x04\x04\x04"), want: []byte("")},
{data: []byte("a\x03\x03\x03"), want: []byte("a")},
{data: []byte("ab\x02\x02"), want: []byte("ab")},
{data: []byte("abc\x01"), want: []byte("abc")},
{data: []byte("abcd\x04\x04\x04\x04"), want: []byte("abcd")},
{data: []byte("abcde\x03\x03\x03"), want: []byte("abcde")},
{data: []byte("abcdefgh\x04\x04\x04\x04"), want: []byte("abcdefgh")},
}
for _, tt := range tests {
got, err := UnpadPKCS5(tt.data, 4)
if err != nil {
t.Errorf("got err %+v while wanted %v", err, tt.want)
} else {
if slices.Compare(got, tt.want) != 0 {
t.Errorf("got %v, want %v", got, tt.want)
}
}
}
}

16
pkg/scrypto/random.go Normal file
View file

@ -0,0 +1,16 @@
package scrypto
import (
"crypto/rand"
"fmt"
)
func RandomBytes(n int) []byte {
data := make([]byte, n)
if _, err := rand.Read(data); err != nil {
panic(fmt.Sprintf("cannot generate random data: %+v", err))
}
return data
}