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

coordinator: add manifest option to seal with unique key or disable sealing #677

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 14 additions & 4 deletions coordinator/clientapi/clientapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/edgelesssys/marblerun/coordinator/manifest"
"github.com/edgelesssys/marblerun/coordinator/quote"
"github.com/edgelesssys/marblerun/coordinator/recovery"
"github.com/edgelesssys/marblerun/coordinator/seal"
"github.com/edgelesssys/marblerun/coordinator/state"
"github.com/edgelesssys/marblerun/coordinator/store"
"github.com/edgelesssys/marblerun/coordinator/store/request"
Expand Down Expand Up @@ -57,7 +58,7 @@ type core interface {

type transactionHandle interface {
BeginTransaction(context.Context) (store.Transaction, error)
SetEncryptionKey([]byte) error
SetEncryptionKey([]byte, seal.Mode) error
SetRecoveryData([]byte)
LoadState() ([]byte, error)
}
Expand Down Expand Up @@ -298,7 +299,7 @@ func (a *ClientAPI) Recover(ctx context.Context, encryptionKey []byte) (keysLeft
}

// all keys are set, we can now load the state
if err := a.txHandle.SetEncryptionKey(secret); err != nil {
if err := a.txHandle.SetEncryptionKey(secret, seal.ModeDisabled); err != nil {
return -1, fmt.Errorf("setting recovery key: %w", err)
}

Expand All @@ -319,6 +320,15 @@ func (a *ClientAPI) Recover(ctx context.Context, encryptionKey []byte) (keysLeft
}
defer rollback()

// set seal mode defined in manifest
mnf, err := txdata.GetManifest()
if err != nil {
return -1, fmt.Errorf("loading manifest from store: %w", err)
}
if err := a.txHandle.SetEncryptionKey(secret, seal.ModeFromString(mnf.Config.SealMode)); err != nil {
return -1, fmt.Errorf("setting recovery key and seal mode: %w", err)
}

rootCert, err := txdata.GetCertificate(constants.SKCoordinatorRootCert)
if err != nil {
return -1, fmt.Errorf("loading root certificate from store: %w", err)
Expand Down Expand Up @@ -400,7 +410,7 @@ func (a *ClientAPI) SetManifest(ctx context.Context, rawManifest []byte) (recove
a.log.Error("could not generate recovery data", zap.Error(err))
return nil, fmt.Errorf("generating recovery data: %w", err)
}
if err := a.txHandle.SetEncryptionKey(encryptionKey); err != nil {
if err := a.txHandle.SetEncryptionKey(encryptionKey, seal.ModeFromString(mnf.Config.SealMode)); err != nil {
a.log.Error("could not set encryption key to seal state", zap.Error(err))
return nil, fmt.Errorf("setting encryption key: %w", err)
}
Expand Down Expand Up @@ -817,7 +827,7 @@ func (a *ClientAPI) FeatureEnabled(ctx context.Context, feature string) bool {
return false
}

return slices.ContainsFunc(mnf.FeatureGates, func(s string) bool {
return slices.ContainsFunc(mnf.Config.FeatureGates, func(s string) bool {
return strings.EqualFold(s, feature)
})
}
93 changes: 84 additions & 9 deletions coordinator/clientapi/clientapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ func TestRecover(t *testing.T) {
_, rootCert := test.MustSetupTestCerts(test.RecoveryPrivateKey)
defaultStore := func() store.Store {
s := stdstore.New(&seal.MockSealer{}, afero.NewMemMapFs(), "")
require.NoError(t, wrapper.New(s).PutCertificate(constants.SKCoordinatorRootCert, rootCert))
wr := wrapper.New(s)
require.NoError(t, wr.PutCertificate(constants.SKCoordinatorRootCert, rootCert))
require.NoError(t, wr.PutRawManifest([]byte(`{}`)))
return s
}

Expand Down Expand Up @@ -548,10 +550,11 @@ func TestRecover(t *testing.T) {

func TestSetManifest(t *testing.T) {
testCases := map[string]struct {
store *fakeStoreTransaction
core *fakeCore
manifest []byte
wantErr bool
store *fakeStoreTransaction
core *fakeCore
manifest []byte
wantErr bool
wantSealMode seal.Mode
}{
"success": {
store: &fakeStoreTransaction{
Expand All @@ -560,7 +563,76 @@ func TestSetManifest(t *testing.T) {
core: &fakeCore{
state: state.AcceptingManifest,
},
manifest: []byte(test.ManifestJSON),
manifest: []byte(test.ManifestJSON),
wantSealMode: seal.ModeProductKey,
},
"seal mode set to product key": {
store: &fakeStoreTransaction{
state: make(map[string][]byte),
},
core: &fakeCore{
state: state.AcceptingManifest,
},
manifest: func() []byte {
var mnf manifest.Manifest
require.NoError(t, json.Unmarshal([]byte(test.ManifestJSON), &mnf))
mnf.Config.SealMode = "ProductKey"
mnfBytes, err := json.Marshal(mnf)
require.NoError(t, err)
return mnfBytes
}(),
wantSealMode: seal.ModeProductKey,
},
"seal mode set to unique key": {
store: &fakeStoreTransaction{
state: make(map[string][]byte),
},
core: &fakeCore{
state: state.AcceptingManifest,
},
manifest: func() []byte {
var mnf manifest.Manifest
require.NoError(t, json.Unmarshal([]byte(test.ManifestJSON), &mnf))
mnf.Config.SealMode = "UniqueKey"
mnfBytes, err := json.Marshal(mnf)
require.NoError(t, err)
return mnfBytes
}(),
wantSealMode: seal.ModeUniqueKey,
},
"seal mode set to disabled": {
store: &fakeStoreTransaction{
state: make(map[string][]byte),
},
core: &fakeCore{
state: state.AcceptingManifest,
},
manifest: func() []byte {
var mnf manifest.Manifest
require.NoError(t, json.Unmarshal([]byte(test.ManifestJSON), &mnf))
mnf.Config.SealMode = "Disabled"
mnfBytes, err := json.Marshal(mnf)
require.NoError(t, err)
return mnfBytes
}(),
wantSealMode: seal.ModeDisabled,
},
"invalid seal mode": {
store: &fakeStoreTransaction{
state: make(map[string][]byte),
},
core: &fakeCore{
state: state.AcceptingManifest,
},
manifest: func() []byte {
var mnf manifest.Manifest
require.NoError(t, json.Unmarshal([]byte(test.ManifestJSON), &mnf))
mnf.Config.SealMode = "foo"
mnfBytes, err := json.Marshal(mnf)
require.NoError(t, err)
return mnfBytes
}(),
wantErr: true,
},
"wrong state": {
store: &fakeStoreTransaction{
Expand Down Expand Up @@ -629,6 +701,7 @@ func TestSetManifest(t *testing.T) {
require.NoError(err)
assert.True(tc.core.unlockCalled)
assert.True(tc.store.commitCalled)
assert.Equal(tc.wantSealMode, tc.store.sealMode)
})
}
}
Expand Down Expand Up @@ -729,7 +802,7 @@ func TestFeatureEnabled(t *testing.T) {
request.Manifest: func() []byte {
var mnf manifest.Manifest
require.NoError(t, json.Unmarshal([]byte(test.ManifestJSON), &mnf))
mnf.FeatureGates = []string{}
mnf.Config.FeatureGates = []string{}
mnfBytes, err := json.Marshal(mnf)
require.NoError(t, err)
return mnfBytes
Expand Down Expand Up @@ -899,7 +972,7 @@ func (s *fakeStore) BeginTransaction(ctx context.Context) (store.Transaction, er
return s.store.BeginTransaction(ctx)
}

func (s *fakeStore) SetEncryptionKey(key []byte) error {
func (s *fakeStore) SetEncryptionKey(key []byte, _ seal.Mode) error {
if s.setEncryptionKeyErr != nil {
return s.setEncryptionKeyErr
}
Expand Down Expand Up @@ -965,6 +1038,7 @@ type fakeStoreTransaction struct {
beginTransactionErr error
setEncryptionKeyCalled bool
setEncryptionKeyErr error
sealMode seal.Mode
loadStateCalled bool
loadStateErr error
setRecoveryDataCalled bool
Expand All @@ -984,8 +1058,9 @@ func (s *fakeStoreTransaction) BeginTransaction(_ context.Context) (store.Transa
return s, s.beginTransactionErr
}

func (s *fakeStoreTransaction) SetEncryptionKey(_ []byte) error {
func (s *fakeStoreTransaction) SetEncryptionKey(_ []byte, mode seal.Mode) error {
s.setEncryptionKeyCalled = true
s.sealMode = mode
return s.setEncryptionKeyErr
}

Expand Down
2 changes: 1 addition & 1 deletion coordinator/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ func (e QuoteError) Error() string {

type transactionHandle interface {
BeginTransaction(context.Context) (store.Transaction, error)
SetEncryptionKey([]byte) error
SetEncryptionKey([]byte, seal.Mode) error
SetRecoveryData([]byte)
LoadState() ([]byte, error)
}
16 changes: 15 additions & 1 deletion coordinator/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ type Manifest struct {
Roles map[string]Role
// TLS contains tags which can be assigned to Marbles to specify which connections should be elevated to TLS
TLS map[string]TLStag
// Config contains optional configuration for the Coordinator.
Config Config
}

// Config contains optional configuration for the Coordinator.
type Config struct {
// SealMode specifies how the data should be sealed. Can be "ProductKey" (default if empty), "UniqueKey", or "Disabled".
SealMode string
// FeatureGates is a list of additional features to enable on the Coordinator.
FeatureGates []string
}
Expand Down Expand Up @@ -493,7 +501,13 @@ func (m Manifest) Check(zaplogger *zap.Logger) error {
}
}

for _, feature := range m.FeatureGates {
switch m.Config.SealMode {
case "", "ProductKey", "UniqueKey", "Disabled":
default:
return fmt.Errorf("unknown seal mode: %s", m.Config.SealMode)
}

for _, feature := range m.Config.FeatureGates {
if feature != FeatureSignQuoteEndpoint {
return fmt.Errorf("unknown feature gate: %s", feature)
}
Expand Down
7 changes: 5 additions & 2 deletions coordinator/seal/mocksealer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ func (s *MockSealer) Seal(unencryptedData []byte, toBeEncrypted []byte) ([]byte,

// SealEncryptionKey implements the Sealer interface.
// Since the MockSealer does not support sealing with an enclave key, it returns the key as is.
func (s *MockSealer) SealEncryptionKey(key []byte) ([]byte, error) {
return key, nil
func (s *MockSealer) SealEncryptionKey(key []byte, mode Mode) ([]byte, error) {
if mode == ModeProductKey || mode == ModeUniqueKey {
return key, nil
}
panic("invariant not met: unexpected mode")
}

// SetEncryptionKey implements the Sealer interface.
Expand Down
32 changes: 32 additions & 0 deletions coordinator/seal/mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Edgeless Systems GmbH.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package seal

import "strings"

// Mode specifies how the data should be sealed.
type Mode uint

const (
// ModeDisabled disables sealing and holds data in memory only.
ModeDisabled Mode = iota
// ModeProductKey enables sealing with the product key.
ModeProductKey
// ModeUniqueKey enables sealing with the unique key.
ModeUniqueKey
)

// ModeFromString returns the Mode value for the given string.
func ModeFromString(mode string) Mode {
switch {
case mode == "", strings.EqualFold(mode, "ProductKey"):
return ModeProductKey
case strings.EqualFold(mode, "UniqueKey"):
return ModeUniqueKey
}
return ModeDisabled
}
2 changes: 1 addition & 1 deletion coordinator/seal/noenclavesealer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (s *NoEnclaveSealer) Seal(unencryptedData []byte, toBeEncrypted []byte) ([]

// SealEncryptionKey implements the Sealer interface.
// Since the NoEnclaveSealer does not support sealing with an enclave key, it returns the key as is.
func (s *NoEnclaveSealer) SealEncryptionKey(key []byte) ([]byte, error) {
func (s *NoEnclaveSealer) SealEncryptionKey(key []byte, _ Mode) ([]byte, error) {
return key, nil
}

Expand Down
20 changes: 10 additions & 10 deletions coordinator/seal/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Sealer interface {
// Unseal decrypts the given data and returns the plain text, as well as the unencrypted metadata.
Unseal(encryptedData []byte) (unencryptedData []byte, decryptedData []byte, err error)
// SealEncryptionKey seals an encryption key using the sealer.
SealEncryptionKey(key []byte) (encryptedKey []byte, err error)
SealEncryptionKey(key []byte, mode Mode) (encryptedKey []byte, err error)
// SetEncryptionKey sets the encryption key of the sealer.
SetEncryptionKey(key []byte)
// UnsealEncryptionKey decrypts an encrypted key.
Expand Down Expand Up @@ -82,23 +82,23 @@ func (s *AESGCMSealer) Seal(unencryptedData []byte, toBeEncrypted []byte) ([]byt
return sealData(unencryptedData, toBeEncrypted, s.encryptionKey)
}

// SealEncryptionKey seals an encryption key with the enclave's product key.
func (s *AESGCMSealer) SealEncryptionKey(encryptionKey []byte) ([]byte, error) {
// Encrypt encryption key with seal key
encryptedKeyData, err := ecrypto.SealWithProductKey(encryptionKey, nil)
if err != nil {
return nil, err
// SealEncryptionKey seals an encryption key with the selected enclave key.
func (s *AESGCMSealer) SealEncryptionKey(encryptionKey []byte, mode Mode) ([]byte, error) {
switch mode {
case ModeProductKey:
return ecrypto.SealWithProductKey(encryptionKey, nil)
case ModeUniqueKey:
return ecrypto.SealWithUniqueKey(encryptionKey, nil)
}

return encryptedKeyData, nil
return nil, errors.New("sealing is disabled")
}

// SetEncryptionKey sets the encryption key of the Sealer.
func (s *AESGCMSealer) SetEncryptionKey(encryptionKey []byte) {
s.encryptionKey = encryptionKey
}

// UnsealEncryptionKey unseals the encryption key using the enclave's product key.
// UnsealEncryptionKey unseals the encryption key.
func (s *AESGCMSealer) UnsealEncryptionKey(encryptedKey []byte) ([]byte, error) {
// Decrypt stored encryption key with seal key
encryptionKey, err := ecrypto.Unseal(encryptedKey, nil)
Expand Down
Loading