Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
33
pkg/security/ecu_files_decrypt.go
Normal file
33
pkg/security/ecu_files_decrypt.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package security
|
||||
|
||||
import "fiskerinc.com/modules/common"
|
||||
|
||||
func DecryptKeys(ecu *common.UpdateManifestECU) error {
|
||||
keys := ecu.ECCKeys
|
||||
if keys == nil {
|
||||
return nil
|
||||
}
|
||||
enc := Encrypt{}
|
||||
encryptor, err := enc.GetEncryptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
priv1, err := encryptor.DecryptChunk(keys.PrivKey1.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys.PrivKey1.SetBytes(priv1)
|
||||
|
||||
priv2, err := encryptor.DecryptChunk(keys.PrivKey2.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys.PrivKey2.SetBytes(priv2)
|
||||
|
||||
priv3, err := encryptor.DecryptChunk(keys.PrivKey3.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys.PrivKey3.SetBytes(priv3)
|
||||
return nil
|
||||
}
|
||||
87
pkg/security/encrypt.go
Normal file
87
pkg/security/encrypt.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type IEncryptor interface {
|
||||
EncryptChunk([]byte) []byte
|
||||
DecryptChunk([]byte) ([]byte, error)
|
||||
EncryptStringToBase64(string) string
|
||||
DecryptBase64ToString(string) (string, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
type Encryptor struct {
|
||||
key []byte
|
||||
iv []byte
|
||||
gcm_encryptor cipher.AEAD
|
||||
block_auth []byte
|
||||
}
|
||||
|
||||
// NewEncryptor create new GCM - expects the key to be 32 bytes
|
||||
func NewEncryptor(encryption_key []byte, auth []byte, nonce []byte) (IEncryptor, []byte, error) {
|
||||
cypher, err := aes.NewCipher(encryption_key)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
gcm, err := cipher.NewGCM(cypher)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
if nonce != nil && len(nonce) != gcm.NonceSize() {
|
||||
return nil, nil, errors.Errorf("nonce is incorrect size %v", gcm.NonceSize())
|
||||
}
|
||||
if nonce == nil {
|
||||
nonce = make([]byte, gcm.NonceSize())
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return &Encryptor{key: encryption_key, iv: nonce, gcm_encryptor: gcm, block_auth: auth}, nonce, nil
|
||||
}
|
||||
|
||||
// EncryptChunk takes a chunk and ecnrypts it using GCM
|
||||
func (s *Encryptor) EncryptChunk(chunk []byte) []byte {
|
||||
return s.gcm_encryptor.Seal(chunk[:0], s.iv, chunk, s.block_auth)
|
||||
}
|
||||
|
||||
// DecryptChunk takes a chunk and decrypts it using GCM
|
||||
func (s *Encryptor) DecryptChunk(chunk []byte) ([]byte, error) {
|
||||
data, err := s.gcm_encryptor.Open(chunk[:0], s.iv, chunk, s.block_auth)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// EncryptStringToBase64 takes a string as input and encrypts it and encodes it for transportability
|
||||
func (s *Encryptor) EncryptStringToBase64(input string) string {
|
||||
data := s.EncryptChunk([]byte(input))
|
||||
return base64.RawStdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// DecryptStringToBase64 takes a string as input and decodes it and decrypts it from transport
|
||||
func (s *Encryptor) DecryptBase64ToString(input string) (string, error) {
|
||||
decoded, err := base64.RawStdEncoding.DecodeString(input)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "request %s", input)
|
||||
}
|
||||
|
||||
decrypted, err := s.DecryptChunk(decoded)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(decrypted), nil
|
||||
}
|
||||
|
||||
func (s *Encryptor) Close() {
|
||||
s.gcm_encryptor = nil
|
||||
s.iv = nil
|
||||
s.key = nil
|
||||
}
|
||||
113
pkg/security/encrypt_test.go
Normal file
113
pkg/security/encrypt_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package security_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/security"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
encryptor, nonce, err := security.NewEncryptor(key, []byte("validate"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
}
|
||||
encrypted := encryptor.EncryptChunk([]byte("Test Me"))
|
||||
|
||||
decryptor, _, err := security.NewEncryptor(key, []byte("validate"), nonce) // use different instance to decrypt
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
}
|
||||
decrypted, err := decryptor.DecryptChunk(encrypted)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
}
|
||||
if string(decrypted) != "Test Me" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", decrypted, len(decrypted))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptStream(t *testing.T) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
encryptor, _, err := security.NewEncryptor(key, []byte("validate"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
return
|
||||
}
|
||||
unique_id := []byte("0123456789abcdef")
|
||||
stream, err := security.NewEncryptedStream(encryptor, security.WithUniqueId(unique_id))
|
||||
assert.Equal(t, err, nil, "failed to create string")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncryptStream", nil, err)
|
||||
return
|
||||
}
|
||||
raw_data := []byte("testme test me testme testme")
|
||||
encrypted_stream := stream.Write(raw_data)
|
||||
|
||||
stream1, err1 := security.NewEncryptedStream(encryptor)
|
||||
if stream1 == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err1)
|
||||
}
|
||||
decryptedStream, err1 := stream1.Read(encrypted_stream)
|
||||
assert.Equal(t, string(decryptedStream), string(raw_data), "Strings don't match")
|
||||
}
|
||||
|
||||
func TestEncryptStringToBase64(t *testing.T) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
encryptor, _, err := security.NewEncryptor(key, []byte("validate"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncryptStringToBase64", nil, err)
|
||||
}
|
||||
|
||||
encrypted := encryptor.EncryptStringToBase64("Test Me")
|
||||
if encrypted == "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncryptStringToBase64", "encrypted", encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptBase64ToString(t *testing.T) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
encryptor, nonce, err := security.NewEncryptor(key, []byte("validate"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestDecryptBase64ToString", nil, err)
|
||||
}
|
||||
|
||||
encrypted := encryptor.EncryptStringToBase64("Test Me")
|
||||
if encrypted == "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestDecryptBase64ToString", "encrypted", encrypted)
|
||||
}
|
||||
|
||||
decryptor, _, err := security.NewEncryptor(key, []byte("validate"), nonce) // use different instance to decrypt
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestDecryptBase64ToString", nil, err)
|
||||
}
|
||||
|
||||
decrypted, err := decryptor.DecryptBase64ToString(encrypted)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestDecryptBase64ToString", nil, err)
|
||||
}
|
||||
if decrypted != "Test Me" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestDecryptBase64ToString", "Test Me", decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptConstruct(t *testing.B) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
_, _, err := security.NewEncryptor(key, []byte("VALIDVIN123456789"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptConstructAndEncrypt(t *testing.B) {
|
||||
key := []byte("MVYLJBSMa8YakAmm66f9carJp80r9S7h")
|
||||
encryptor, _, err := security.NewEncryptor(key, []byte("VALIDVIN123456789"), nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestEncrypt", nil, err)
|
||||
}
|
||||
|
||||
_ = encryptor.EncryptChunk([]byte("Test Me"))
|
||||
}
|
||||
98
pkg/security/encryptedstream.go
Normal file
98
pkg/security/encryptedstream.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type IEncryptedStream interface {
|
||||
Write(raw_data []byte) []byte
|
||||
Read(raw_data []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
const blockSize = 4096
|
||||
const headerSize = 16
|
||||
|
||||
type EncryptedStream struct {
|
||||
gcm_encrypter IEncryptor
|
||||
header []byte
|
||||
header_applied bool
|
||||
}
|
||||
type StreamOption func(EncryptedStream) (EncryptedStream, error)
|
||||
|
||||
func NewEncryptedStream(encryptor IEncryptor, options ...StreamOption) (IEncryptedStream, error) {
|
||||
var err error
|
||||
stream := EncryptedStream{gcm_encrypter: encryptor, header: nil, header_applied: false}
|
||||
for _, option := range options {
|
||||
stream, err = option(stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &stream, nil
|
||||
}
|
||||
|
||||
func WithUniqueId(uniqueid []byte) StreamOption {
|
||||
return func(stream EncryptedStream) (EncryptedStream, error) {
|
||||
if len(uniqueid) == headerSize {
|
||||
stream.header = uniqueid
|
||||
stream.header_applied = false
|
||||
return stream, nil
|
||||
}
|
||||
return stream, errors.New("invalid file id - must be 16 bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EncryptedStream) Write(rawData []byte) []byte {
|
||||
var length = len(rawData)
|
||||
highWatermark := 0
|
||||
byteStream := make([]byte, 0)
|
||||
index := 0
|
||||
if !s.header_applied {
|
||||
byteStream = append(byteStream, s.header...)
|
||||
s.header_applied = true
|
||||
}
|
||||
for index < length {
|
||||
highWatermark = int(math.Min(float64(length-index), float64(blockSize)))
|
||||
slice := s.gcm_encrypter.EncryptChunk(rawData[index:highWatermark])
|
||||
chunk_size := fmt.Sprintf("%04x", len(slice))
|
||||
byteStream = append(byteStream, chunk_size...)
|
||||
byteStream = append(byteStream, slice...)
|
||||
index += highWatermark
|
||||
}
|
||||
return byteStream
|
||||
}
|
||||
|
||||
func (s *EncryptedStream) Read(rawData []byte) ([]byte, error) {
|
||||
var length int64 = int64(len(rawData))
|
||||
byteStream := make([]byte, 0)
|
||||
if !s.header_applied && length < headerSize {
|
||||
return nil, errors.New("invalid stream")
|
||||
}
|
||||
var index int64 = 0
|
||||
|
||||
// read header
|
||||
if !s.header_applied {
|
||||
s.header = rawData[0:headerSize]
|
||||
s.header_applied = true
|
||||
index = headerSize
|
||||
}
|
||||
for index < length {
|
||||
nextBlockSize := "0x" + string(rawData[index:index+4])
|
||||
byte_slice_length, err := strconv.ParseInt(nextBlockSize, 0, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index += 4 // move index by 4 to actual data
|
||||
slice, err := s.gcm_encrypter.DecryptChunk(rawData[index:(index + byte_slice_length)])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byteStream = append(byteStream, slice...)
|
||||
index += byte_slice_length
|
||||
}
|
||||
return byteStream, nil
|
||||
}
|
||||
30
pkg/security/encryptor.go
Normal file
30
pkg/security/encryptor.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fiskerinc.com/modules/utils/envtool"
|
||||
)
|
||||
|
||||
var envOnce sync.Once
|
||||
var master_key = []byte{}
|
||||
var master_auth = []byte{}
|
||||
var master_nouce = []byte{}
|
||||
|
||||
type Encrypt struct{}
|
||||
|
||||
func (enc *Encrypt) GetEncryptor() (IEncryptor, error) {
|
||||
envOnce.Do(func() {
|
||||
master_key = []byte(envtool.GetEnv("MASTER_KEY", "REPLACE_ME_REPLACE_ME_REPLACE_ME"))
|
||||
master_auth = []byte(envtool.GetEnv("MASTER_KEY_AUTH", "REPLACE_ME_REPLACE_ME"))
|
||||
master_nouce = []byte(envtool.GetEnv("MASTER_KEY_NOUNCE", "_REPLACE_ME_"))
|
||||
})
|
||||
key, auth, nonce := enc.getMasterKey()
|
||||
|
||||
encryptor, _, err := NewEncryptor([]byte(key), []byte(auth), []byte(nonce))
|
||||
return encryptor, err
|
||||
}
|
||||
|
||||
func (enc *Encrypt) getMasterKey() ([]byte, []byte, []byte) {
|
||||
return master_key, master_auth, master_nouce
|
||||
}
|
||||
7
pkg/security/errors.go
Normal file
7
pkg/security/errors.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrInvalidSessionID = errors.New("invalid session ID")
|
||||
67
pkg/security/salter.go
Normal file
67
pkg/security/salter.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fiskerinc.com/modules/redis"
|
||||
"fiskerinc.com/modules/utils/envtool"
|
||||
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func NewSalter(auth string) (ISalter, error) {
|
||||
var encryptor IEncryptor
|
||||
|
||||
key := []byte(envtool.GetEnv("MASTER_KEY", "REPLACE_ME_REPLACE_ME_REPLACE_ME"))
|
||||
byteAuth := []byte(auth)
|
||||
nonce := []byte(envtool.GetEnv("MASTER_KEY_NONCE", "_REPLACE_ME_"))
|
||||
|
||||
encryptor, _, err := NewEncryptor(key, byteAuth, nonce)
|
||||
return &Salter{encryptor: encryptor, auth: auth}, err
|
||||
}
|
||||
|
||||
type ISalter interface {
|
||||
GenerateSessionID(string, string) string
|
||||
ValidateSessionID(string) error
|
||||
CheckSessionID(redis.Client, string) error
|
||||
}
|
||||
|
||||
type Salter struct {
|
||||
encryptor IEncryptor
|
||||
auth string
|
||||
}
|
||||
|
||||
func (s *Salter) GenerateSessionID(key string, salt string) string {
|
||||
return s.encryptor.EncryptStringToBase64(fmt.Sprintf("%s:%s", key, salt))
|
||||
}
|
||||
|
||||
func (s *Salter) ValidateSessionID(sessionID string) error {
|
||||
if sessionID == "" {
|
||||
return ErrInvalidSessionID
|
||||
}
|
||||
data, err := s.encryptor.DecryptBase64ToString(sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vin := strings.Split(data, ":")[0]
|
||||
if vin != s.auth {
|
||||
return ErrInvalidSessionID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Salter) CheckSessionID(client redis.Client, vin string) error {
|
||||
sessionID, err := redigo.String(client.Get(redis.HMISessionKey(vin)))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
err = s.ValidateSessionID(sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
53
pkg/security/salter_test.go
Normal file
53
pkg/security/salter_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package security_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/redis"
|
||||
"fiskerinc.com/modules/security"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
func TestSalter(t *testing.T) {
|
||||
redis.MockRedisConnection()
|
||||
mockRedis := &redis.Connection{}
|
||||
|
||||
var salter security.ISalter
|
||||
vin := "VALIDVIN123"
|
||||
|
||||
salter, err := security.NewSalter(vin)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := salter.GenerateSessionID(vin, "SALT")
|
||||
if sessionID == "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", "", sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
err = salter.ValidateSessionID(sessionID)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = salter.ValidateSessionID("INVALIDSESSIONID")
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = salter.ValidateSessionID("")
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = salter.CheckSessionID(mockRedis, "Vin")
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestSalter", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user