From c7b3162ef30a7b2a9876f86ee85c621a0ca12106 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:46:38 +0100 Subject: [PATCH 1/7] Added TEE package. --- rolling-shutter/.gitignore | 4 + rolling-shutter/tee/sealedsecret.go | 168 +++++++++++++++++++++++ rolling-shutter/tee/sealedsecret_test.go | 100 ++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 rolling-shutter/tee/sealedsecret.go create mode 100644 rolling-shutter/tee/sealedsecret_test.go diff --git a/rolling-shutter/.gitignore b/rolling-shutter/.gitignore index 48c2ef12..7217e545 100644 --- a/rolling-shutter/.gitignore +++ b/rolling-shutter/.gitignore @@ -9,3 +9,7 @@ /godoc.idx /go.work /go.work.sum + +enclave.json +private.pem +public.pem \ No newline at end of file diff --git a/rolling-shutter/tee/sealedsecret.go b/rolling-shutter/tee/sealedsecret.go new file mode 100644 index 00000000..a7377983 --- /dev/null +++ b/rolling-shutter/tee/sealedsecret.go @@ -0,0 +1,168 @@ +/* +TEE-based hardware encryption functionality for secret data. + +This package contains functionality for handling secrets so that they can be +securely stored on — and loaded from — disk. This is a simple serialisation +step, and depending on whether TEE hardware is present (and whether the code is +running in an enclave), these functions either perform hardware sealing and +unsealing, or they simply pass the original data along. This ensures that old +persistence/config files can be loaded as usual, but new files written from +within an enclave will have their secrets sealed. + +This package is designed to be minimally invasive for the rest of the codebase. +It does not protect against replay attacks where old persistences are loaded. +The sealed data can only be unsealed by the same CPU that sealed it and only from within a binary that is signed with the same key as the binary that sealed it. + +The threat model of this package is to protect a node operator from hackers with admin access that seek to leak his secrets. It does not provide trust in the +node operator's honesty to the outside world. It merely makes the operator trust his own machine to not leak secrets sealed using this package. +*/ +package tee + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + "testing" + + egoattestation "github.com/edgelesssys/ego/attestation" + "github.com/edgelesssys/ego/ecrypto" + egoenclave "github.com/edgelesssys/ego/enclave" +) + +// If we are running in SGX, this contains the enclave identity. +var tpmIdentity *egoattestation.Report = nil + +func init() { + ident, hasTPM, err := QueryTPMIdentity() + if err != nil { + panic(fmt.Sprintf("Could not query TPM identity: %v", err)) + } + if hasTPM { + tpmIdentity = &ident + } +} + +func HasTEE() bool { + return tpmIdentity != nil +} + +func QueryTPMIdentity() (identity egoattestation.Report, exists bool, err error) { + // Get some information about where we're running (if in a TEE and some + // details about the TEE we're running in) + identity, err = egoenclave.GetSelfReport() + exists = true + if err != nil { + if err.Error() == "OE_UNSUPPORTED" { + exists = false + err = nil + } else { + err = fmt.Errorf("Could not get self-report: %w", err) + } + } + return +} + +// All sealed blobs are prefixed with this string, to distinguish them from +// normal blobs. Normal data should never start with this string. +const sealedPrefix = "sealed://" + +func blobIsSealed(blob []byte) (sealed bool, payload []byte) { + if len(blob) >= len(sealedPrefix) && string(blob[:len(sealedPrefix)]) == sealedPrefix { + return true, blob[len(sealedPrefix):] + } + return false, blob +} + +// If we are in a TEE, always seal the secret. Otherwise, simply convert to hex. +func SealSecretAsHex(secret []byte) (string, error) { + return SealSecretAsCustomText(secret, hex.EncodeToString) +} + +func SealSecretAsCustomText(secret []byte, encoder func([]byte) string) (string, error) { + if sealed, _ := blobIsSealed(secret); sealed { + return "", errors.New("Blob looks like it is already sealed") + } + + if !HasTEE() { + return encoder(secret), nil + } + + sealed, err := ecrypto.SealWithProductKey(secret, nil) + if err != nil { + return "", fmt.Errorf("failed to seal secret: %w", err) + } + + return sealedPrefix + encoder(sealed), nil +} + +// If we are in a TEE, always seal the secret. Otherwise, returns the raw bytes. +func SealSecret(secret []byte) ([]byte, error) { + if sealed, _ := blobIsSealed(secret); sealed { + return nil, errors.New("Blob looks like it is already sealed") + } + + if !HasTEE() { + return secret, nil + } + + sealed, err := ecrypto.SealWithProductKey(secret, nil) + if err != nil { + return nil, fmt.Errorf("failed to seal secret: %w", err) + } + + ret := []byte(sealedPrefix) + return append(ret, sealed...), nil +} + +// If we are in a TEE, unseal the secret, if it is sealed. Otherwise, simply parse the hex string. +func UnsealSecretFromHex(str string) ([]byte, error) { + return UnsealSecretFromCustomText(str, hex.DecodeString) +} + +func UnsealSecretFromCustomText(str string, decoder func(string) ([]byte, error)) ([]byte, error) { + if cut, ok := strings.CutPrefix(str, sealedPrefix); ok { + bytes, err := decoder(cut) + if err != nil { + return nil, err + } + + if !HasTEE() { + return nil, errors.New("Cannot unseal secret: not in a TEE") + } + + return ecrypto.Unseal(bytes, nil) + } + + return decoder(str) +} + +func UnsealSecret(secret []byte) ([]byte, error) { + if sealed, payload := blobIsSealed(secret); sealed { + if !HasTEE() { + return nil, errors.New("Cannot unseal secret: not in a TEE") + } + + return ecrypto.Unseal(payload, nil) + } + + return secret, nil +} + +// This function is only for testing purposes, when the non-TEE behaviour is +// needed somewhere. +// +// Executes the passed function with temporarily disabled TEE functionality +// (during the execution, HasTEE() returns false). If this function is called +// outside of tests, it panics. +func DoWithoutTEE(the_thing func()) { + if !testing.Testing() { + panic("This function is for tests only") + } + + restore := tpmIdentity + defer func() { tpmIdentity = restore }() + tpmIdentity = nil + + the_thing() +} diff --git a/rolling-shutter/tee/sealedsecret_test.go b/rolling-shutter/tee/sealedsecret_test.go new file mode 100644 index 00000000..ec91172f --- /dev/null +++ b/rolling-shutter/tee/sealedsecret_test.go @@ -0,0 +1,100 @@ +package tee + +import ( + "strings" + "testing" + + "gotest.tools/v3/assert" +) + +// Start of utlities + +func RequiresTEE(t *testing.T) { + if !HasTEE() { + t.Skip("Test requires TEE, but we are not running on a TEE") + } +} + +func RunWithoutTEE(t *testing.T, name string, test func(t *testing.T)) { + t.Helper() + t.Run(name, func(t *testing.T) { + DoWithoutTEE(func() { test(t) }) + }) +} + +func RunWithTEE(t *testing.T, name string, test func(t *testing.T)) { + t.Helper() + t.Run(name, func(t *testing.T) { + RequiresTEE(t) + test(t) + }) +} + +// End of utilities + +// Start of test cases + +func TestBlobIsSealed(t *testing.T) { + t.Run("Sealed input", func(t *testing.T) { + input := append([]byte(sealedPrefix), 1, 2, 3, 4) + isSealed, payload := blobIsSealed(input) + assert.Equal(t, isSealed, true, "isSealed") + assert.DeepEqual(t, payload, []byte{1, 2, 3, 4}) + }) + + t.Run("Unsealed input", func(t *testing.T) { + input := []byte{5, 6, 7, 8} + isSealed, payload := blobIsSealed(input) + assert.Equal(t, isSealed, false, "isSealed") + assert.DeepEqual(t, payload, []byte{5, 6, 7, 8}) + }) +} + +func TestSealSecretAsHex(t *testing.T) { + RunWithoutTEE(t, "Without TEE", func(t *testing.T) { + input := []byte{0x12, 0x34, 0x56, 0x78} + output, err := SealSecretAsHex(input) + assert.NilError(t, err, "seal") + assert.Equal(t, output, "12345678", "seal") + + reverse, err := UnsealSecretFromHex(output) + assert.NilError(t, err, "unseal") + assert.DeepEqual(t, reverse, input) + }) + + RunWithTEE(t, "HW sealing", func(t *testing.T) { + input := []byte{0x9a, 0xbc, 0xde, 0xf0} + output, err := SealSecretAsHex(input) + assert.NilError(t, err, "seal") + assert.Assert(t, strings.HasPrefix(output, sealedPrefix), "seal") + + reverse, err := UnsealSecretFromHex(output) + assert.NilError(t, err, "unseal") + assert.DeepEqual(t, reverse, input) + }) +} + +func TestSealSecret(t *testing.T) { + RunWithoutTEE(t, "Without TEE", func(t *testing.T) { + input := []byte{0x12, 0x34, 0x56, 0x78} + output, err := SealSecret(input) + assert.NilError(t, err, "seal") + assert.DeepEqual(t, output, input) + + reverse, err := UnsealSecret(output) + assert.NilError(t, err, "unseal") + assert.DeepEqual(t, reverse, input) + }) + + RunWithTEE(t, "HW sealing", func(t *testing.T) { + input := []byte{0x9a, 0xbc, 0xde, 0xf0} + output, err := SealSecret(input) + assert.NilError(t, err, "seal") + isSealed, _ := blobIsSealed(output) + assert.Assert(t, isSealed, "seal") + + reverse, err := UnsealSecret(output) + assert.NilError(t, err, "unseal") + assert.DeepEqual(t, reverse, input) + }) +} From f9844dad504f10418f70790e0dc9448fc28947aa Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:46:44 +0100 Subject: [PATCH 2/7] [TEE] shdb: seal secrets in helper functions. --- rolling-shutter/shdb/db.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/rolling-shutter/shdb/db.go b/rolling-shutter/shdb/db.go index 6842f6c2..eea9d93f 100644 --- a/rolling-shutter/shdb/db.go +++ b/rolling-shutter/shdb/db.go @@ -15,6 +15,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" "github.com/shutter-network/shutter/shlib/puredkg" "github.com/shutter-network/shutter/shlib/shcrypto" ) @@ -90,13 +91,18 @@ func EncodePureDKG(p *puredkg.PureDKG) ([]byte, error) { if err != nil { return nil, err } - return buff.Bytes(), nil + return tee.SealSecret(buff.Bytes()) } func DecodePureDKG(data []byte) (*puredkg.PureDKG, error) { - buf := bytes.NewBuffer(data) + unsealed, err := tee.UnsealSecret(data) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(unsealed) p := &puredkg.PureDKG{} - err := gob.NewDecoder(buf).Decode(p) + err = gob.NewDecoder(buf).Decode(p) if err != nil { return nil, err } @@ -127,25 +133,36 @@ func EncodePureDKGResult(result *puredkg.Result) ([]byte, error) { if err != nil { return nil, err } - return buf.Bytes(), nil + return tee.SealSecret(buf.Bytes()) } func DecodePureDKGResult(b []byte) (*puredkg.Result, error) { + unsealed, err := tee.UnsealSecret(b) + if err != nil { + return nil, err + } + res := puredkg.Result{} - err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&res) + + err = gob.NewDecoder(bytes.NewBuffer(unsealed)).Decode(&res) if err != nil { return nil, err } return &res, nil } -func EncodeEpochSecretKeyShare(share *shcrypto.EpochSecretKeyShare) []byte { - return share.Marshal() +func EncodeEpochSecretKeyShare(share *shcrypto.EpochSecretKeyShare) ([]byte, error) { + return tee.SealSecret(share.Marshal()) } func DecodeEpochSecretKeyShare(b []byte) (*shcrypto.EpochSecretKeyShare, error) { + unsealed, err := tee.UnsealSecret(b) + if err != nil { + return nil, err + } + share := new(shcrypto.EpochSecretKeyShare) - err := share.Unmarshal(b) + err = share.Unmarshal(unsealed) if err != nil { return nil, err } From 36446df6182e7352793bbf439a601949b9bd9385 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:46:49 +0100 Subject: [PATCH 3/7] [TEE] database: seal secret key shares. --- rolling-shutter/keyper/database/extend.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/rolling-shutter/keyper/database/extend.go b/rolling-shutter/keyper/database/extend.go index 2822ea7a..d9c47749 100644 --- a/rolling-shutter/keyper/database/extend.go +++ b/rolling-shutter/keyper/database/extend.go @@ -12,6 +12,7 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg" "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" "github.com/shutter-network/rolling-shutter/rolling-shutter/shmsg" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" ) func GetKeyperIndex(addr common.Address, keypers []string) (uint64, bool) { @@ -31,10 +32,16 @@ func (bc *TendermintBatchConfig) KeyperIndex(addr common.Address) (uint64, bool) func (q *Queries) InsertDecryptionKeysMsg(ctx context.Context, msg *p2pmsg.DecryptionKeys) error { for _, key := range msg.Keys { identityPreimage := identitypreimage.IdentityPreimage(key.IdentityPreimage) + + sealedKey, err := tee.SealSecret(key.Key) + if err != nil { + return err + } + tag, err := q.InsertDecryptionKey(ctx, InsertDecryptionKeyParams{ Eon: int64(msg.Eon), EpochID: identityPreimage.Bytes(), - DecryptionKey: key.Key, + DecryptionKey: sealedKey, }) if err != nil { return errors.Wrapf(err, "failed to insert decryption key for identity %s", identityPreimage) @@ -49,11 +56,16 @@ func (q *Queries) InsertDecryptionKeysMsg(ctx context.Context, msg *p2pmsg.Decry func (q *Queries) InsertDecryptionKeySharesMsg(ctx context.Context, msg *p2pmsg.DecryptionKeyShares) error { for _, share := range msg.GetShares() { - err := q.InsertDecryptionKeyShare(ctx, InsertDecryptionKeyShareParams{ + sealedShare, err := tee.SealSecret(share.Share) + if err != nil { + return err + } + + err = q.InsertDecryptionKeyShare(ctx, InsertDecryptionKeyShareParams{ Eon: int64(msg.Eon), EpochID: share.IdentityPreimage, KeyperIndex: int64(msg.KeyperIndex), - DecryptionKeyShare: share.Share, + DecryptionKeyShare: sealedShare, }) if err != nil { return errors.Wrapf( From 835dbbb0a55faa07b5197b19a6b628761346d211 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:46:57 +0100 Subject: [PATCH 4/7] [TEE] medley/keys: sealing for secret keys. This prevents secret keys from being stored in plain text, if running in a TEE. --- rolling-shutter/medley/encodeable/keys/ecdsa.go | 6 ++++-- rolling-shutter/medley/encodeable/keys/ed25519.go | 6 ++++-- rolling-shutter/medley/encodeable/keys/libp2p.go | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rolling-shutter/medley/encodeable/keys/ecdsa.go b/rolling-shutter/medley/encodeable/keys/ecdsa.go index 26f6bf01..0e339a61 100644 --- a/rolling-shutter/medley/encodeable/keys/ecdsa.go +++ b/rolling-shutter/medley/encodeable/keys/ecdsa.go @@ -11,6 +11,7 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/hex" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" ) func GenerateECDSAKey(src io.Reader) (*ECDSAPrivate, error) { @@ -60,7 +61,7 @@ func (k *ECDSAPrivate) Equal(b *ECDSAPrivate) bool { } func (k *ECDSAPrivate) UnmarshalText(b []byte) error { - dec, err := hex.DecodeHex(b) + dec, err := tee.UnsealSecretFromHex(string(b)) if err != nil { return err } @@ -73,7 +74,8 @@ func (k *ECDSAPrivate) UnmarshalText(b []byte) error { } func (k *ECDSAPrivate) MarshalText() ([]byte, error) { - return hex.EncodeHex(k.Bytes()), nil + str, err := tee.SealSecretAsHex(k.Bytes()) + return []byte(str), err } func (k *ECDSAPrivate) String() string { diff --git a/rolling-shutter/medley/encodeable/keys/ed25519.go b/rolling-shutter/medley/encodeable/keys/ed25519.go index e0ad8573..ecc27126 100644 --- a/rolling-shutter/medley/encodeable/keys/ed25519.go +++ b/rolling-shutter/medley/encodeable/keys/ed25519.go @@ -8,6 +8,7 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/hex" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" ) type nullReader struct{} @@ -69,7 +70,7 @@ func (k *Ed25519Private) Equal(b *Ed25519Private) bool { } func (k *Ed25519Private) UnmarshalText(b []byte) error { - seed, err := hex.DecodeHex(b) + seed, err := tee.UnsealSecretFromHex(string(b)) if err != nil { return err } @@ -85,7 +86,8 @@ func (k *Ed25519Private) UnmarshalText(b []byte) error { } func (k *Ed25519Private) MarshalText() ([]byte, error) { - return hex.EncodeHex(k.Key.Seed()), nil + str, err := tee.SealSecretAsHex(k.Key.Seed()) + return []byte(str), err } func (k *Ed25519Private) String() string { diff --git a/rolling-shutter/medley/encodeable/keys/libp2p.go b/rolling-shutter/medley/encodeable/keys/libp2p.go index 86c17c28..dee79c49 100644 --- a/rolling-shutter/medley/encodeable/keys/libp2p.go +++ b/rolling-shutter/medley/encodeable/keys/libp2p.go @@ -10,6 +10,7 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/address" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" ) func GenerateLibp2pPrivate(src io.Reader) (*Libp2pPrivate, error) { @@ -103,7 +104,7 @@ func (k *Libp2pPrivate) Equal(b *Libp2pPrivate) bool { } func (k *Libp2pPrivate) UnmarshalText(b []byte) error { - dec, err := crypto.ConfigDecodeKey(string(b)) + dec, err := tee.UnsealSecretFromCustomText(string(b), crypto.ConfigDecodeKey) if err != nil { return errors.Wrap(errFailedUnmarshalLibp2pPrivate, err.Error()) } @@ -127,8 +128,8 @@ func (k *Libp2pPrivate) MarshalText() ([]byte, error) { if err != nil { return []byte{}, errors.Wrap(errFailedMarshalLibp2pPrivate, err.Error()) } - enc := crypto.ConfigEncodeKey(b) - return []byte(enc), nil + str, err := tee.SealSecretAsCustomText(b, crypto.ConfigEncodeKey) + return []byte(str), err } func (k *Libp2pPrivate) String() string { From b7c063592596b3bac7bb3ff40bd8ed4892ad1d82 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:47:04 +0100 Subject: [PATCH 5/7] [TEE] smobserver: sealing of PolyEval.Eval. --- rolling-shutter/keyper/smobserver/smstate.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rolling-shutter/keyper/smobserver/smstate.go b/rolling-shutter/keyper/smobserver/smstate.go index 1d50db9e..1a27d190 100644 --- a/rolling-shutter/keyper/smobserver/smstate.go +++ b/rolling-shutter/keyper/smobserver/smstate.go @@ -28,6 +28,7 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" "github.com/shutter-network/rolling-shutter/rolling-shutter/shmsg" + "github.com/shutter-network/rolling-shutter/rolling-shutter/tee" ) type Config interface { @@ -227,7 +228,13 @@ func (st *ShuttermintState) sendPolyEvals(ctx context.Context, queries *database if !ok { panic("key not loaded into ShuttermintState") } - encrypted, err := ecies.Encrypt(rand.Reader, pubkey, eval.Eval, nil, nil) + + plainEval, err := tee.UnsealSecret(eval.Eval) + if err != nil { + return err + } + + encrypted, err := ecies.Encrypt(rand.Reader, pubkey, plainEval, nil, nil) if err != nil { return err } @@ -391,10 +398,15 @@ func (st *ShuttermintState) startPhase1Dealing( } for _, eval := range polyEvals { + sealedEval, err := tee.SealSecret(shdb.EncodeBigint(eval.Eval)) + if err != nil { + return err + } + err = queries.InsertPolyEval(ctx, database.InsertPolyEvalParams{ Eon: int64(eon), ReceiverAddress: shdb.EncodeAddress(dkg.keypers[eval.Receiver]), - Eval: shdb.EncodeBigint(eval.Eval), + Eval: sealedEval, }) if err != nil { return err From b43fbf04350db33528e123b37e722ebe834901e2 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:47:13 +0100 Subject: [PATCH 6/7] go get --- rolling-shutter/go.mod | 14 ++++++++------ rolling-shutter/go.sum | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/rolling-shutter/go.mod b/rolling-shutter/go.mod index d8b7e690..f4164b49 100644 --- a/rolling-shutter/go.mod +++ b/rolling-shutter/go.mod @@ -10,6 +10,7 @@ require ( github.com/bitwurx/jrpc2 v0.0.0-20220302204700-52c6dbbeb536 github.com/deckarep/golang-set/v2 v2.1.0 github.com/deepmap/oapi-codegen v1.9.1 + github.com/edgelesssys/ego v1.7.0 github.com/ethereum/go-ethereum v1.13.11 github.com/ferranbt/fastssz v0.1.3 github.com/getkin/kin-openapi v0.87.0 @@ -39,7 +40,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/supranational/blst v0.3.12 github.com/tendermint/go-amino v0.16.0 github.com/tendermint/tendermint v0.37.0-rc2 @@ -49,9 +50,9 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.27.0 go.opentelemetry.io/otel/trace v1.27.0 go.opentelemetry.io/proto/otlp v1.3.1 - golang.org/x/crypto v0.23.0 + golang.org/x/crypto v0.32.0 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.10.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 gotest.tools v2.2.0+incompatible @@ -93,6 +94,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect @@ -238,9 +240,9 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/grpc v1.64.0 // indirect diff --git a/rolling-shutter/go.sum b/rolling-shutter/go.sum index d3f37a9d..f8c9cfdb 100644 --- a/rolling-shutter/go.sum +++ b/rolling-shutter/go.sum @@ -186,6 +186,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/edgelesssys/ego v1.7.0 h1:NzCiKZKalHbeRgG7+11xgADgOPR8laSxxazOH303QVw= +github.com/edgelesssys/ego v1.7.0/go.mod h1:xBU369lGvxWTuMLlJv3tT6bI66hrpkCFn/292zzl7U4= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= @@ -243,6 +245,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -927,6 +931,8 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.12 h1:Vfas2U2CFHhniv2QkUm2OVa1+pGTdqtpqm9NnhUUbZ8= @@ -1068,6 +1074,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1191,6 +1199,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1272,6 +1282,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1298,6 +1310,8 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1372,6 +1386,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From ea503a8e7f93bf6d3c9ff7a906ce3cb9fec77976 Mon Sep 17 00:00:00 2001 From: Steffen Rattay Date: Tue, 4 Feb 2025 02:54:49 +0100 Subject: [PATCH 7/7] Copied TEE README from perun-network/shutter. --- rolling-shutter/tee/README.md | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 rolling-shutter/tee/README.md diff --git a/rolling-shutter/tee/README.md b/rolling-shutter/tee/README.md new file mode 100644 index 00000000..3c0a5a5e --- /dev/null +++ b/rolling-shutter/tee/README.md @@ -0,0 +1,71 @@ +# TEE-fortified Keyper + +The Keyper software has been fortified with TEE capabilities (currently only supporting Intel® SGX™), securing the secret key storage using hardware-based encryption. +This raises the confidence level of the non-collusion security assumption of the Keypers, as it makes it harder for attackers to access the key material. +Now, it is less likely that an honest Keyper operator can get compromised by an attacker, ensuring his honest behaviour. + +## How it works + +**Hardware-based process isolation**  +TEEs use a hardware-level process isolation mechanism that prevents even the operating system and kernel from inspecting or manipulating the process' memory. +This means that secrets are completely safe from even administrator-level attackers, as long as they reside in process memory (assuming the hardware is not compromised). + +**Hardware-based encryption**  +Additionally, we employ the _sealing_ mechanism which allows us to encrypt and authenticate data using hardware-derived keys. +This mechanism allows us to use encryption keys that can either only be derived by a specific executable, or executables signed by a specific authority. +Using this, we can securely store secrets on the disk without the risk of them being deciphered by an attacker. + +### Warning + +Create a backup of the old persistence files before trying to run this in production on SGX. +The TEE keyper can load old persistence files, but the new persistence files written by it are sealed and therefore no longer compatible with non-TEE keypers. +It is therefore best to create a backup on cold storage somewhere. + +## Roadmap + +1. Done: Currently, besides ensuring that it can run inside SGX to benefit from strong process isolation, the Keyper code has been adapted to seal its checkpoints before persisting them to the disk. +2. Coming up: In the next step, we eliminate the trust in the blockchain node that is providing the keyper with information about the outside world. We fully verify the blockchain consensus inside the TEE, and we check the merkle proofs for all blocks to ensure correctness and completeness of the blockchain events. This mainly prevents attacks that try to make the keyper reveal its key shares too early by providing fake blockchain data. +3. Future perspective: An outsider's confidence and trust in a Keyper operator can be improved using further measures such as remote attestation (provable computation), which would allow the Keyper to publicly prove that he is running his node inside a TEE. Further fortifications could then fortify the initial cryptographic setup. + +The currently planned fortifications only ensure that the Keyper operator can trust the software running on his machine. +As the Keypers already have access to their keys, if they wanted to, they could use those to misbehave at any time. +Since the keypers cannot provably forget their keys, they cannot prove that their keys are entirely controlled by the TEE. +Compatibility-breaking changes to the Shutter protocol would have to be enacted to further improve trust. + +# How to set up Shutter to use SGX + +1. Clone the Shutter repository and install Go. +2. Install EGo, following the [official instructions](https://docs.edgeless.systems/ego/getting-started/install) for your OS. +3. Build shutter using `GO=ego-go make build`. + + +**Testing**  +To run shutter tests inside SGX, create a compiled test executable: + +```sh +ego-go test -c +``` + +Note that currently, this has to be done for each package separately. + +**Running an executable inside the TEE**  +Then, sign and run the resulting test executable, run + +```sh +ego sign package.test +ego run package.test +``` + +This signs the executable and generates an `enclave.json` file in the current directory, which contains the launch configuration for the TEE enclave. +The resulting executable can also be run normally without SGX by invoking it like a normal executable, and conversely, can also be run on a non-SGX CPU in a TEE-emulation mode by setting `OE_SIMULATION=1` before running it using `ego run`. +This step is the same for test executables, as well as the normal shutter executables. + +For maximum security, we recommend that there is a trusted central authority responsible for the `ego sign` step of the keyper executable. +This creates an additional layer of security, because an attacker in possession of the signing key can create executables that can unseal the persistence files. +Therefore, the signing key should either be on cold storage, and each keyper signs for himself, or it should be one official key handled by a trusted party. + +## TEE-aware programming + +Currently, the TEE-specific functionality is contained within the `keyper/tee` package. +Using `tee.HasTEE()`, code can query whether it is running inside a TEE (but does not differentiate between true hardware support and the emulation mode). +This way, code paths can be introduced that will only activate when the executable has been explicitly invoked as a TEE-capable process using `ego run`, and so the same code can stay compatible with non-TEE hardware. \ No newline at end of file