Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cosmosDB in the gateway and encrypt data with encryption key generated inside the enclave #2104

Merged
merged 19 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/manual-deploy-obscuro-gateway.yml
Original file line number Diff line number Diff line change
Expand Up @@ -296,5 +296,5 @@ jobs:
"${{ env.DOCKER_BUILD_TAG_GATEWAY }}" \
ego run /home/ten/go-ten/tools/walletextension/main/main \
-host=0.0.0.0 -port=80 -portWS=81 -nodeHost="${{ env.L2_RPC_URL_VALIDATOR }}" -verbose=true \
-logPath=sys_out -dbType=mariaDB -dbConnectionURL="obscurouser:${{ secrets.OBSCURO_GATEWAY_MARIADB_USER_PWD }}@tcp(obscurogateway-mariadb-${{ github.event.inputs.testnet_type }}.uksouth.cloudapp.azure.com:3306)/ogdb" \
-logPath=sys_out -dbType=cosmosDB -dbConnectionURL="${{ secrets.COSMOS_DB_CONNECTION_STRING }}" \
-rateLimitUserComputeTime="${{ env.GATEWAY_RATE_LIMIT_USER_COMPUTE_TIME }}" -rateLimitWindow="${{ env.GATEWAY_RATE_LIMIT_WINDOW }}" -maxConcurrentRequestsPerUser="${{ env.GATEWAY_MAX_CONCURRENT_REQUESTS_PER_USER }}" '
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21.11
replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible

require (
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.1.0
github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3
github.com/Microsoft/go-winio v0.6.2
github.com/andybalholm/brotli v1.1.1
Expand Down Expand Up @@ -55,6 +56,9 @@ require (

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/DataDog/zstd v1.5.6 // indirect
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
Expand Down Expand Up @@ -102,6 +106,7 @@ require (
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/gorilla/mux v1.8.1 // indirect
Expand Down
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.1.0 h1:c726lgbwpwFBuj+Fyrwuh/vUilqFo+hUAOUNjsKj5DI=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.1.0/go.mod h1:WzFGxuepAtZIZtQbz8/WviJycLMKJHpaEAqcXONxlag=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3 h1:KzMvCFet0baw6uJnxTE/His8YeRgaxlASd4/ISuTvzI=
github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3/go.mod h1:OiwyYqbtMkQH+VzA4b8lI+qHnExJy0fIdz+59/8nFes=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
Expand Down Expand Up @@ -159,6 +173,11 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -279,6 +298,8 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
12 changes: 12 additions & 0 deletions tools/walletextension/common/common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"crypto/rand"
"encoding/json"
"fmt"

Expand All @@ -15,6 +16,8 @@ import (
gethlog "github.com/ethereum/go-ethereum/log"
)

const EncryptionKeySize = 32

// PrivateKeyToCompressedPubKey converts *ecies.PrivateKey to compressed PubKey ([]byte with length 33)
func PrivateKeyToCompressedPubKey(prvKey *ecies.PrivateKey) []byte {
ecdsaPublicKey := prvKey.PublicKey.ExportECDSA()
Expand Down Expand Up @@ -76,3 +79,12 @@ func (r *RPCRequest) Clone() *RPCRequest {
Params: r.Params,
}
}

func GenerateRandomKey() ([]byte, error) {
key := make([]byte, EncryptionKeySize)
_, err := rand.Read(key)
if err != nil {
return nil, err
}
return key, nil
}
1 change: 1 addition & 0 deletions tools/walletextension/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type Config struct {
RateLimitUserComputeTime time.Duration
RateLimitWindow time.Duration
RateLimitMaxConcurrentRequests int
Debug bool
}
15 changes: 8 additions & 7 deletions tools/walletextension/common/db_types.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package common

type AccountDB struct {
AccountAddress []byte
Signature []byte
SignatureType int
type GWUserDB struct {
UserId []byte `json:"userId"`
PrivateKey []byte `json:"privateKey"`
Accounts []GWAccountDB `json:"accounts"`
}

type UserDB struct {
UserID []byte
PrivateKey []byte
type GWAccountDB struct {
AccountAddress []byte `json:"accountAddress"`
Signature []byte `json:"signature"`
SignatureType int `json:"signatureType"`
}
6 changes: 6 additions & 0 deletions tools/walletextension/enclave.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

FROM ghcr.io/edgelesssys/ego-dev:v1.5.3 AS build-base

# Install ca-certificates package and update it
RUN apt-get update && apt-get install -y \
ca-certificates \
&& update-ca-certificates


# setup container data structure
RUN mkdir -p /home/ten/go-ten

Expand Down
65 changes: 65 additions & 0 deletions tools/walletextension/encryption/encryption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package encryption

import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
)

// Encryptor provides AES-GCM encryption/decryption with the following characteristics:
// - Uses AES-256-GCM (Galois/Counter Mode) with a 32-byte key
// - Generates a random 12-byte nonce for each encryption operation using crypto/rand
// - The nonce is prepended to the ciphertext output from Encrypt() and is generated
// using crypto/rand.Reader for cryptographically secure random values
//
// Additionally provides HMAC-SHA256 hashing functionality:
// - Uses the same 32-byte key as the encryption operations
// - Generates a 32-byte (256-bit) message authentication code
// - Suitable for creating secure message digests and verifying data integrity
type Encryptor struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a good comment describing the encryption approach. AES-GCM with key size, nonce size. How we generate a nonce for each.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a comment

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks.

gcm cipher.AEAD
key []byte
}

func NewEncryptor(key []byte) (*Encryptor, error) {
if len(key) != 32 {
return nil, fmt.Errorf("key must be 32 bytes long")
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return &Encryptor{gcm: gcm, key: key}, nil
}

func (e *Encryptor) Encrypt(plaintext []byte) ([]byte, error) {
nonce := make([]byte, e.gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return e.gcm.Seal(nonce, nonce, plaintext, nil), nil
}

func (e *Encryptor) Decrypt(ciphertext []byte) ([]byte, error) {
if len(ciphertext) < e.gcm.NonceSize() {
return nil, errors.New("ciphertext too short")
}
nonce, ciphertext := ciphertext[:e.gcm.NonceSize()], ciphertext[e.gcm.NonceSize():]
return e.gcm.Open(nil, nonce, ciphertext, nil)
}

func (e *Encryptor) HashWithHMAC(data []byte) []byte {
h := hmac.New(sha256.New, e.key)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be reused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be reused (it has it's internal state and by reusing we might get different outputs)

h.Write(data)
return h.Sum(nil)
}
115 changes: 115 additions & 0 deletions tools/walletextension/encryption/encryption_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package encryption

import (
"bytes"
"crypto/rand"
"testing"
)

func TestNewEncryptor(t *testing.T) {
key := make([]byte, 32) // 256-bit key
_, err := rand.Read(key)
if err != nil {
t.Fatalf("Failed to generate random key: %v", err)
}

encryptor, err := NewEncryptor(key)
if err != nil {
t.Fatalf("NewEncryptor failed: %v", err)
}

if encryptor == nil {
t.Fatal("NewEncryptor returned nil")
}
}

func TestEncryptDecrypt(t *testing.T) {
key := make([]byte, 32) // 256-bit key
_, err := rand.Read(key)
if err != nil {
t.Fatalf("Failed to generate random key: %v", err)
}

encryptor, err := NewEncryptor(key)
if err != nil {
t.Fatalf("NewEncryptor failed: %v", err)
}

plaintext := []byte("Hello, World!")

ciphertext, err := encryptor.Encrypt(plaintext)
if err != nil {
t.Fatalf("Encryption failed: %v", err)
}

decrypted, err := encryptor.Decrypt(ciphertext)
if err != nil {
t.Fatalf("Decryption failed: %v", err)
}

if !bytes.Equal(plaintext, decrypted) {
t.Fatalf("Decrypted text does not match original plaintext")
}
}

func TestEncryptDecryptEmptyString(t *testing.T) {
key := make([]byte, 32) // 256-bit key
_, err := rand.Read(key)
if err != nil {
t.Fatalf("Failed to generate random key: %v", err)
}

encryptor, err := NewEncryptor(key)
if err != nil {
t.Fatalf("NewEncryptor failed: %v", err)
}

plaintext := []byte("")

ciphertext, err := encryptor.Encrypt(plaintext)
if err != nil {
t.Fatalf("Encryption of empty string failed: %v", err)
}

decrypted, err := encryptor.Decrypt(ciphertext)
if err != nil {
t.Fatalf("Decryption of empty string failed: %v", err)
}

if !bytes.Equal(plaintext, decrypted) {
t.Fatalf("Decrypted empty string does not match original")
}
}

func TestDecryptInvalidCiphertext(t *testing.T) {
key := make([]byte, 32) // 256-bit key
_, err := rand.Read(key)
if err != nil {
t.Fatalf("Failed to generate random key: %v", err)
}

encryptor, err := NewEncryptor(key)
if err != nil {
t.Fatalf("NewEncryptor failed: %v", err)
}

invalidCiphertext := []byte("This is not a valid ciphertext")

_, err = encryptor.Decrypt(invalidCiphertext)
if err == nil {
t.Fatal("Decryption of invalid ciphertext should have failed, but didn't")
}
}

func TestNewEncryptorInvalidKeySize(t *testing.T) {
invalidKey := make([]byte, 31) // Invalid key size (not 16, 24, or 32 bytes)
_, err := rand.Read(invalidKey)
if err != nil {
t.Fatalf("Failed to generate random key: %v", err)
}

_, err = NewEncryptor(invalidKey)
if err == nil {
t.Fatal("NewEncryptor should have failed with invalid key size, but didn't")
}
}
6 changes: 6 additions & 0 deletions tools/walletextension/main/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ const (
rateLimitMaxConcurrentRequestsName = "maxConcurrentRequestsPerUser"
rateLimitMaxConcurrentRequestsDefault = 3
rateLimitMaxConcurrentRequestsUsage = "Number of concurrent requests allowed per user. Default: 3"

debugFlagName = "debug"
debugFlagDefault = false
debugFlagUsage = "Flag to enable debug mode"
)

func parseCLIArgs() wecommon.Config {
Expand All @@ -91,6 +95,7 @@ func parseCLIArgs() wecommon.Config {
rateLimitUserComputeTime := flag.Duration(rateLimitUserComputeTimeName, rateLimitUserComputeTimeDefault, rateLimitUserComputeTimeUsage)
rateLimitWindow := flag.Duration(rateLimitWindowName, rateLimitWindowDefault, rateLimitWindowUsage)
rateLimitMaxConcurrentRequests := flag.Int(rateLimitMaxConcurrentRequestsName, rateLimitMaxConcurrentRequestsDefault, rateLimitMaxConcurrentRequestsUsage)
debugFlag := flag.Bool(debugFlagName, debugFlagDefault, debugFlagUsage)
flag.Parse()

return wecommon.Config{
Expand All @@ -109,5 +114,6 @@ func parseCLIArgs() wecommon.Config {
RateLimitUserComputeTime: *rateLimitUserComputeTime,
RateLimitWindow: *rateLimitWindow,
RateLimitMaxConcurrentRequests: *rateLimitMaxConcurrentRequests,
Debug: *debugFlag,
}
}
12 changes: 2 additions & 10 deletions tools/walletextension/main/enclave.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,8 @@
],
"files": [
{
"source": "../storage/database/mariadb/001_init.sql",
"target": "/home/ten/go-ten/tools/walletextension/storage/database/mariadb/001_init.sql"
},
{
"source": "../storage/database/mariadb/002_store_incoming_txs.sql",
"target": "/home/ten/go-ten/tools/walletextension/storage/database/mariadb/002_store_incoming_txs.sql"
},
{
"source": "../storage/database/mariadb/003_add_signature_type.sql",
"target": "/home/ten/go-ten/tools/walletextension/storage/database/mariadb/003_add_signature_type.sql"
"source": "/etc/ssl/certs/ca-certificates.crt",
"target": "/etc/ssl/certs/ca-certificates.crt"
}
]
}
Loading
Loading