From 696f2042fe69bcae8ca5d649ffb6fac9674f9a95 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Mon, 29 Jan 2024 09:44:56 +0100 Subject: [PATCH] go/keymanager: Move master and ephemeral secrets code to extension --- .changelog/5544.trivial.md | 0 go/consensus/cometbft/api/app.go | 6 + .../cometbft/apps/keymanager/genesis.go | 96 +--- .../cometbft/apps/keymanager/keymanager.go | 408 ++++------------- .../cometbft/apps/keymanager/messages.go | 7 +- .../cometbft/apps/keymanager/messages_test.go | 13 +- .../cometbft/apps/keymanager/query.go | 31 +- .../cometbft/apps/keymanager/secrets/ext.go | 79 ++++ .../apps/keymanager/secrets/genesis.go | 89 ++++ .../cometbft/apps/keymanager/secrets/query.go | 57 +++ .../secrets/state/interop/interop.go | 162 +++++++ .../keymanager/{ => secrets}/state/state.go | 38 +- .../{ => secrets}/state/state_test.go | 34 +- .../apps/keymanager/secrets/status.go | 345 ++++++++++++++ .../status_test.go} | 48 +- .../{transactions.go => secrets/txs.go} | 57 +-- .../txs_test.go} | 48 +- .../apps/keymanager/state/interop/interop.go | 154 +------ .../apps/supplementarysanity/checks.go | 8 +- .../cometbft/keymanager/keymanager.go | 145 +----- .../cometbft/keymanager/secrets/client.go | 164 +++++++ go/genesis/genesis_test.go | 5 +- go/keymanager/api/api.go | 420 +----------------- go/keymanager/api/grpc.go | 380 +--------------- go/keymanager/secrets/api.go | 396 +++++++++++++++++ go/keymanager/{api => secrets}/api_test.go | 2 +- go/keymanager/secrets/grpc.go | 392 ++++++++++++++++ go/keymanager/{api => secrets}/policy_sgx.go | 2 +- .../{api => secrets}/sanity_check.go | 2 +- go/keymanager/{api => secrets}/secret.go | 2 +- go/keymanager/{api => secrets}/secret_test.go | 2 +- go/oasis-node/cmd/debug/dumpdb/dumpdb.go | 2 +- go/oasis-node/cmd/genesis/genesis.go | 10 +- go/oasis-node/cmd/genesis/migrate.go | 6 +- go/oasis-node/cmd/keymanager/keymanager.go | 37 +- go/oasis-test-runner/oasis/cli/keymanager.go | 4 +- .../e2e/runtime/helpers_keymanager.go | 50 +-- .../scenario/e2e/runtime/helpers_runtime.go | 8 +- .../scenario/e2e/runtime/keymanager_client.go | 18 +- .../runtime/keymanager_ephemeral_secrets.go | 4 +- .../e2e/runtime/keymanager_master_secrets.go | 10 +- .../e2e/runtime/keymanager_replicate.go | 6 +- .../runtime/keymanager_rotation_failure.go | 2 +- go/runtime/host/protocol/types.go | 4 +- go/runtime/host/tests/tester.go | 9 +- go/runtime/registry/host.go | 10 +- go/worker/common/committee/keymanager.go | 6 +- go/worker/keymanager/api/api.go | 6 +- go/worker/keymanager/config/config.go | 8 + go/worker/keymanager/secrets.go | 99 +++-- runtime/src/consensus/state/keymanager.rs | 4 +- 51 files changed, 2111 insertions(+), 1784 deletions(-) create mode 100644 .changelog/5544.trivial.md create mode 100644 go/consensus/cometbft/apps/keymanager/secrets/ext.go create mode 100644 go/consensus/cometbft/apps/keymanager/secrets/genesis.go create mode 100644 go/consensus/cometbft/apps/keymanager/secrets/query.go create mode 100644 go/consensus/cometbft/apps/keymanager/secrets/state/interop/interop.go rename go/consensus/cometbft/apps/keymanager/{ => secrets}/state/state.go (84%) rename go/consensus/cometbft/apps/keymanager/{ => secrets}/state/state_test.go (65%) create mode 100644 go/consensus/cometbft/apps/keymanager/secrets/status.go rename go/consensus/cometbft/apps/keymanager/{keymanager_test.go => secrets/status_test.go} (85%) rename go/consensus/cometbft/apps/keymanager/{transactions.go => secrets/txs.go} (86%) rename go/consensus/cometbft/apps/keymanager/{transactions_test.go => secrets/txs_test.go} (84%) create mode 100644 go/consensus/cometbft/keymanager/secrets/client.go create mode 100644 go/keymanager/secrets/api.go rename go/keymanager/{api => secrets}/api_test.go (98%) create mode 100644 go/keymanager/secrets/grpc.go rename go/keymanager/{api => secrets}/policy_sgx.go (99%) rename go/keymanager/{api => secrets}/sanity_check.go (98%) rename go/keymanager/{api => secrets}/secret.go (99%) rename go/keymanager/{api => secrets}/secret_test.go (99%) diff --git a/.changelog/5544.trivial.md b/.changelog/5544.trivial.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/consensus/cometbft/api/app.go b/go/consensus/cometbft/api/app.go index ee4c3f2730f..e03b942c0da 100644 --- a/go/consensus/cometbft/api/app.go +++ b/go/consensus/cometbft/api/app.go @@ -118,6 +118,12 @@ type Extension interface { // ExecuteTx executes a transaction. ExecuteTx(*Context, *transaction.Transaction) error + // InitChain initializes the blockchain with validators and other + // info from CometBFT. + // + // Note: Errors are irrecoverable and will result in a panic. + InitChain(*Context, cmtabcitypes.RequestInitChain, *genesis.Document) error + // BeginBlock signals the beginning of a block. // // Note: Errors are irrecoverable and will result in a panic. diff --git a/go/consensus/cometbft/apps/keymanager/genesis.go b/go/consensus/cometbft/apps/keymanager/genesis.go index 9a33862fb72..ff7461defc4 100644 --- a/go/consensus/cometbft/apps/keymanager/genesis.go +++ b/go/consensus/cometbft/apps/keymanager/genesis.go @@ -1,111 +1,25 @@ package keymanager import ( - "context" "encoding/json" - "errors" - "fmt" "github.com/cometbft/cometbft/abci/types" - "github.com/oasisprotocol/oasis-core/go/common" tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" genesis "github.com/oasisprotocol/oasis-core/go/genesis/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" - registry "github.com/oasisprotocol/oasis-core/go/registry/api" ) -func (app *keymanagerApplication) InitChain(ctx *tmapi.Context, _ types.RequestInitChain, doc *genesis.Document) error { - st := doc.KeyManager - - b, _ := json.Marshal(st) +func (app *keymanagerApplication) InitChain(ctx *tmapi.Context, req types.RequestInitChain, doc *genesis.Document) error { + b, _ := json.Marshal(doc.KeyManager) ctx.Logger().Debug("InitChain: Genesis state", "state", string(b), ) - state := keymanagerState.NewMutableState(ctx.State()) - - if err := state.SetConsensusParameters(ctx, &st.Parameters); err != nil { - return fmt.Errorf("cometbft/keymanager: failed to set consensus parameters: %w", err) - } - - epoch, err := app.state.GetCurrentEpoch(ctx) - if err != nil { - return fmt.Errorf("cometbft/keymanager: couldn't get current epoch: %w", err) - } - - // TODO: The better thing to do would be to move the registry init - // before the keymanager, and just query the registry for the runtime - // list. - regSt := doc.Registry - rtMap := make(map[common.Namespace]*registry.Runtime) - for _, rt := range regSt.Runtimes { - err := registry.VerifyRuntime(®St.Parameters, ctx.Logger(), rt, true, false, epoch) - if err != nil { - ctx.Logger().Error("InitChain: Invalid runtime", - "err", err, - ) - continue - } - - if rt.Kind == registry.KindKeyManager { - rtMap[rt.ID] = rt + for _, ext := range app.exts { + if err := ext.InitChain(ctx, req, doc); err != nil { + return err } } - var toEmit []*keymanager.Status - for i, v := range st.Statuses { - if v == nil { - return fmt.Errorf("InitChain: Status index %d is nil", i) - } - rt := rtMap[v.ID] - if rt == nil { - ctx.Logger().Error("InitChain: State for unknown key manager runtime", - "id", v.ID, - ) - continue - } - - ctx.Logger().Debug("InitChain: Registering genesis key manager", - "id", v.ID, - ) - - // Make sure the Nodes field is empty when applying genesis state. - if v.Nodes != nil { - ctx.Logger().Error("InitChain: Genesis key manager has nodes", - "id", v.ID, - ) - return errors.New("cometbft/keymanager: genesis key manager has nodes") - } - - // Set, enqueue for emit. - if err := state.SetStatus(ctx, v); err != nil { - return fmt.Errorf("cometbft/keymanager: failed to set status: %w", err) - } - toEmit = append(toEmit, v) - } - - if len(toEmit) > 0 { - ctx.EmitEvent(tmapi.NewEventBuilder(app.Name()).TypedAttribute(&keymanager.StatusUpdateEvent{ - Statuses: toEmit, - })) - } - return nil } - -func (kq *keymanagerQuerier) Genesis(ctx context.Context) (*keymanager.Genesis, error) { - statuses, err := kq.state.Statuses(ctx) - if err != nil { - return nil, err - } - - // Remove the Nodes field of each Status. - for _, status := range statuses { - status.Nodes = nil - } - - gen := keymanager.Genesis{Statuses: statuses} - return &gen, nil -} diff --git a/go/consensus/cometbft/apps/keymanager/keymanager.go b/go/consensus/cometbft/apps/keymanager/keymanager.go index 30c4b1d74dc..8a2f4063a33 100644 --- a/go/consensus/cometbft/apps/keymanager/keymanager.go +++ b/go/consensus/cometbft/apps/keymanager/keymanager.go @@ -1,422 +1,184 @@ package keymanager import ( - "bytes" - "encoding/hex" "fmt" "github.com/cometbft/cometbft/abci/types" - "golang.org/x/crypto/sha3" - beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" - "github.com/oasisprotocol/oasis-core/go/common/cbor" - "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" - "github.com/oasisprotocol/oasis-core/go/common/node" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets" registryapp "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" - "github.com/oasisprotocol/oasis-core/go/keymanager/api" registry "github.com/oasisprotocol/oasis-core/go/registry/api" ) -// minProposalReplicationPercent is the minimum percentage of enclaves in the key manager committee -// that must replicate the proposal for the next master secret before it is accepted. -const minProposalReplicationPercent = 66 - -var emptyHashSha3 = sha3.Sum256(nil) - type keymanagerApplication struct { state tmapi.ApplicationState + + exts []tmapi.Extension + methods []transaction.MethodName + extsByMethod map[transaction.MethodName]tmapi.Extension } +// Name implements api.Application. func (app *keymanagerApplication) Name() string { return AppName } +// ID implements api.Application. func (app *keymanagerApplication) ID() uint8 { return AppID } +// Methods implements api.Application. func (app *keymanagerApplication) Methods() []transaction.MethodName { - return api.Methods + return app.methods } +// Blessed implements api.Application. func (app *keymanagerApplication) Blessed() bool { return false } +// Dependencies implements api.Application. func (app *keymanagerApplication) Dependencies() []string { return []string{registryapp.AppName} } -func (app *keymanagerApplication) OnRegister(state tmapi.ApplicationState, _ tmapi.MessageDispatcher) { +// OnRegister implements api.Application. +func (app *keymanagerApplication) OnRegister(state tmapi.ApplicationState, md tmapi.MessageDispatcher) { app.state = state + + for _, ext := range app.exts { + ext.OnRegister(state, md) + } } +// OnCleanup implements api.Application. func (app *keymanagerApplication) OnCleanup() {} +// BeginBlock implements api.Application. func (app *keymanagerApplication) BeginBlock(ctx *tmapi.Context) error { - if changed, epoch := app.state.EpochChanged(ctx); changed { - return app.onEpochChange(ctx, epoch) + // Prioritize application-specific logic. + if changed, _ := app.state.EpochChanged(ctx); changed { + if err := suspendRuntimes(ctx); err != nil { + return err + } } + + // Proceed with extension-specific logic. + for _, ext := range app.exts { + if err := ext.BeginBlock(ctx); err != nil { + return err + } + } + return nil } +// ExecuteMessage implements api.Application. func (app *keymanagerApplication) ExecuteMessage(*tmapi.Context, interface{}, interface{}) (interface{}, error) { return nil, fmt.Errorf("keymanager: unexpected message") } +// ExecuteTx implements api.Application. func (app *keymanagerApplication) ExecuteTx(ctx *tmapi.Context, tx *transaction.Transaction) error { - state := keymanagerState.NewMutableState(ctx.State()) - ctx.SetPriority(AppPriority) - switch tx.Method { - case api.MethodUpdatePolicy: - var sigPol api.SignedPolicySGX - if err := cbor.Unmarshal(tx.Body, &sigPol); err != nil { - return api.ErrInvalidArgument - } - return app.updatePolicy(ctx, state, &sigPol) - case api.MethodPublishMasterSecret: - var sigSec api.SignedEncryptedMasterSecret - if err := cbor.Unmarshal(tx.Body, &sigSec); err != nil { - return api.ErrInvalidArgument - } - return app.publishMasterSecret(ctx, state, &sigSec) - case api.MethodPublishEphemeralSecret: - var sigSec api.SignedEncryptedEphemeralSecret - if err := cbor.Unmarshal(tx.Body, &sigSec); err != nil { - return api.ErrInvalidArgument - } - return app.publishEphemeralSecret(ctx, state, &sigSec) - default: + ext, ok := app.extsByMethod[tx.Method] + if !ok { return fmt.Errorf("keymanager: invalid method: %s", tx.Method) } + + return ext.ExecuteTx(ctx, tx) } +// EndBlock implements api.Application. func (app *keymanagerApplication) EndBlock(*tmapi.Context) (types.ResponseEndBlock, error) { return types.ResponseEndBlock{}, nil } -func (app *keymanagerApplication) onEpochChange(ctx *tmapi.Context, epoch beacon.EpochTime) error { - // Query the runtime and node lists. +// suspendRuntimes suspends runtimes if registering entities no longer possess enough stake +// to cover the entity and runtime deposits. +func suspendRuntimes(ctx *tmapi.Context) error { regState := registryState.NewMutableState(ctx.State()) - runtimes, _ := regState.Runtimes(ctx) - nodes, _ := regState.Nodes(ctx) - registry.SortNodeList(nodes) params, err := regState.ConsensusParameters(ctx) if err != nil { return fmt.Errorf("failed to get consensus parameters: %w", err) } + if params.DebugBypassStake { + return nil + } - var stakeAcc *stakingState.StakeAccumulatorCache - if !params.DebugBypassStake { - stakeAcc, err = stakingState.NewStakeAccumulatorCache(ctx) - if err != nil { - return fmt.Errorf("failed to create stake accumulator cache: %w", err) - } - defer stakeAcc.Discard() + stakeAcc, err := stakingState.NewStakeAccumulatorCache(ctx) + if err != nil { + return fmt.Errorf("failed to create stake accumulator cache: %w", err) } + defer stakeAcc.Discard() - // Recalculate all the key manager statuses. - // - // Note: This assumes that once a runtime is registered, it never expires. - var toEmit []*api.Status - state := keymanagerState.NewMutableState(ctx.State()) + runtimes, _ := regState.Runtimes(ctx) for _, rt := range runtimes { if rt.Kind != registry.KindKeyManager { continue } - // Suspend the runtime in case the registering entity no longer has enough stake to cover - // the entity and runtime deposits. - if !params.DebugBypassStake && rt.GovernanceModel != registry.GovernanceConsensus { - acctAddr := rt.StakingAddress() - if acctAddr == nil { - // This should never happen. - ctx.Logger().Error("unknown runtime governance model", - "rt_id", rt.ID, - "gov_model", rt.GovernanceModel, - ) - return fmt.Errorf("unknown runtime governance model on runtime %s: %s", rt.ID, rt.GovernanceModel) - } - - if err = stakeAcc.CheckStakeClaims(*acctAddr); err != nil { - ctx.Logger().Debug("insufficient stake for key manager runtime operation", - "err", err, - "entity", rt.EntityID, - "account", *acctAddr, - ) - - // Suspend runtime. - if err := regState.SuspendRuntime(ctx, rt.ID); err != nil { - return err - } - - continue - } + if rt.GovernanceModel == registry.GovernanceConsensus { + continue } - var forceEmit bool - oldStatus, err := state.Status(ctx, rt.ID) - switch err { - case nil: - case api.ErrNoSuchStatus: - // This must be a new key manager runtime. - forceEmit = true - oldStatus = &api.Status{ - ID: rt.ID, - } - default: - // This is fatal, as it suggests state corruption. - ctx.Logger().Error("failed to query key manager status", - "id", rt.ID, - "err", err, + acctAddr := rt.StakingAddress() + if acctAddr == nil { + // This should never happen. + ctx.Logger().Error("unknown runtime governance model", + "rt_id", rt.ID, + "gov_model", rt.GovernanceModel, ) - return fmt.Errorf("failed to query key manager status: %w", err) + return fmt.Errorf("unknown runtime governance model on runtime %s: %s", rt.ID, rt.GovernanceModel) } - secret, err := state.MasterSecret(ctx, rt.ID) - if err != nil && err != api.ErrNoSuchMasterSecret { - ctx.Logger().Error("failed to query key manager master secret", - "id", rt.ID, - "err", err, - ) - return fmt.Errorf("failed to query key manager master secret: %w", err) + if err = stakeAcc.CheckStakeClaims(*acctAddr); err == nil { + continue } - newStatus := app.generateStatus(ctx, rt, oldStatus, secret, nodes, params, epoch) - if forceEmit || !bytes.Equal(cbor.Marshal(oldStatus), cbor.Marshal(newStatus)) { - ctx.Logger().Debug("status updated", - "id", newStatus.ID, - "is_initialized", newStatus.IsInitialized, - "is_secure", newStatus.IsSecure, - "generation", newStatus.Generation, - "rotation_epoch", newStatus.RotationEpoch, - "checksum", hex.EncodeToString(newStatus.Checksum), - "rsk", newStatus.RSK, - "nodes", newStatus.Nodes, - ) + ctx.Logger().Debug("insufficient stake for key manager runtime operation", + "err", err, + "entity", rt.EntityID, + "account", *acctAddr, + ) - // Set, enqueue for emit. - if err = state.SetStatus(ctx, newStatus); err != nil { - return fmt.Errorf("failed to set key manager status: %w", err) - } - toEmit = append(toEmit, newStatus) + if err := regState.SuspendRuntime(ctx, rt.ID); err != nil { + return err } } - // Note: It may be a good idea to sweep statuses that don't have runtimes, - // but as runtime registrations last forever, so this shouldn't be possible. - - // Emit the update event if required. - if len(toEmit) > 0 { - ctx.EmitEvent(tmapi.NewEventBuilder(app.Name()).TypedAttribute(&api.StatusUpdateEvent{ - Statuses: toEmit, - })) - } - return nil } -func (app *keymanagerApplication) generateStatus( // nolint: gocyclo - ctx *tmapi.Context, - kmrt *registry.Runtime, - oldStatus *api.Status, - secret *api.SignedEncryptedMasterSecret, - nodes []*node.Node, - params *registry.ConsensusParameters, - epoch beacon.EpochTime, -) *api.Status { - status := &api.Status{ - ID: kmrt.ID, - IsInitialized: oldStatus.IsInitialized, - IsSecure: oldStatus.IsSecure, - Generation: oldStatus.Generation, - RotationEpoch: oldStatus.RotationEpoch, - Checksum: oldStatus.Checksum, - Policy: oldStatus.Policy, - } - - // Data needed to count the nodes that have replicated the proposal for the next master secret. - var ( - nextGeneration uint64 - nextChecksum []byte - nextRSK *signature.PublicKey - updatedNodes []signature.PublicKey - ) - nextGeneration = status.NextGeneration() - if secret != nil && secret.Secret.Generation == nextGeneration && secret.Secret.Epoch == epoch { - nextChecksum = secret.Secret.Secret.Checksum - } - - // Compute the policy hash to reject nodes that are not up-to-date. - var rawPolicy []byte - if status.Policy != nil { - rawPolicy = cbor.Marshal(status.Policy) - } - policyHash := sha3.Sum256(rawPolicy) - - ts := ctx.Now() - height := uint64(ctx.BlockHeight()) - - // Construct a key manager committee. A node is added to the committee if it supports - // at least one version of the key manager runtime and if all supported versions conform - // to the key manager status fields. -nextNode: - for _, n := range nodes { - if n.IsExpired(uint64(epoch)) { - continue - } - if !n.HasRoles(node.RoleKeyManager) { - continue - } - - secretReplicated := true - isInitialized := status.IsInitialized - isSecure := status.IsSecure - RSK := status.RSK - nRSK := nextRSK - - var numVersions int - for _, nodeRt := range n.Runtimes { - if !nodeRt.ID.Equal(&kmrt.ID) { - continue - } - - vars := []interface{}{ - "id", kmrt.ID, - "node_id", n.ID, - "version", nodeRt.Version, - } - - var teeOk bool - if nodeRt.Capabilities.TEE == nil { - teeOk = kmrt.TEEHardware == node.TEEHardwareInvalid - } else { - teeOk = kmrt.TEEHardware == nodeRt.Capabilities.TEE.Hardware - } - if !teeOk { - ctx.Logger().Error("TEE hardware mismatch", vars...) - continue nextNode - } - - initResponse, err := api.VerifyExtraInfo(ctx.Logger(), n.ID, kmrt, nodeRt, ts, height, params) - if err != nil { - ctx.Logger().Error("failed to validate ExtraInfo", append(vars, "err", err)...) - continue nextNode - } - - // Skip nodes with mismatched policy. - var nodePolicyHash [api.ChecksumSize]byte - switch len(initResponse.PolicyChecksum) { - case 0: - nodePolicyHash = emptyHashSha3 - case api.ChecksumSize: - copy(nodePolicyHash[:], initResponse.PolicyChecksum) - default: - ctx.Logger().Error("failed to parse policy checksum", append(vars, "err", err)...) - continue nextNode - } - if policyHash != nodePolicyHash { - ctx.Logger().Error("Policy checksum mismatch for runtime", vars...) - continue nextNode - } - - // Set immutable status fields that cannot change after initialization. - if !isInitialized { - // The first version gets to be the source of truth. - isInitialized = true - isSecure = initResponse.IsSecure - } - - // Skip nodes with mismatched status fields. - if initResponse.IsSecure != isSecure { - ctx.Logger().Error("Security status mismatch for runtime", vars...) - continue nextNode - } - - // Skip nodes with mismatched checksum. - // Note that a node needs to register with an empty checksum if no master secrets - // have been generated so far. Otherwise, if secrets have been generated, the node - // needs to register with a checksum computed over all the secrets generated so far - // since the key manager's checksum is updated after every master secret rotation. - if !bytes.Equal(initResponse.Checksum, status.Checksum) { - ctx.Logger().Error("Checksum mismatch for runtime", vars...) - continue nextNode - } - - // Update mutable status fields that can change on epoch transitions. - if RSK == nil { - // The first version with non-nil runtime signing key gets to be the source of truth. - RSK = initResponse.RSK +func (app *keymanagerApplication) registerExtensions(exts ...tmapi.Extension) { + for _, ext := range exts { + for _, m := range ext.Methods() { + if _, ok := app.extsByMethod[m]; ok { + panic(fmt.Sprintf("keymanager: method already registered: %s", m)) } - - // Skip nodes with mismatched runtime signing key. - // For backward compatibility we always allow nodes without runtime signing key. - if initResponse.RSK != nil && !initResponse.RSK.Equal(*RSK) { - ctx.Logger().Error("Runtime signing key mismatch for runtime", vars) - continue nextNode - } - - // Check if all versions have replicated the last master secret, - // derived the same RSK and are ready to move to the next generation. - if !bytes.Equal(initResponse.NextChecksum, nextChecksum) { - secretReplicated = false - } - if nRSK == nil { - nRSK = initResponse.NextRSK - } - if initResponse.NextRSK != nil && !initResponse.NextRSK.Equal(*nRSK) { - secretReplicated = false - } - - numVersions++ - } - - if numVersions == 0 { - continue - } - if !isInitialized { - panic("the key manager must be initialized") - } - if secretReplicated { - nextRSK = nRSK - updatedNodes = append(updatedNodes, n.ID) - } - - // If the key manager is not initialized, the first verified node gets to be the source - // of truth, every other node will sync off it. - if !status.IsInitialized { - status.IsInitialized = true - status.IsSecure = isSecure - } - status.RSK = RSK - status.Nodes = append(status.Nodes, n.ID) - } - - // Accept the proposal if the majority of the nodes have replicated - // the proposal for the next master secret. - if numNodes := len(status.Nodes); numNodes > 0 && nextChecksum != nil { - percent := len(updatedNodes) * 100 / numNodes - if percent >= minProposalReplicationPercent { - status.Generation = nextGeneration - status.RotationEpoch = epoch - status.Checksum = nextChecksum - status.RSK = nextRSK - status.Nodes = updatedNodes + app.extsByMethod[m] = ext + app.methods = append(app.methods, m) } + app.exts = append(app.exts, ext) } - - return status } // New constructs a new keymanager application instance. func New() tmapi.Application { - return &keymanagerApplication{} + app := keymanagerApplication{ + exts: make([]tmapi.Extension, 0), + methods: make([]transaction.MethodName, 0), + extsByMethod: make(map[transaction.MethodName]tmapi.Extension), + } + + app.registerExtensions(secrets.New(app.Name())) + + return &app } diff --git a/go/consensus/cometbft/apps/keymanager/messages.go b/go/consensus/cometbft/apps/keymanager/messages.go index d3f71259235..ac2d8ead63b 100644 --- a/go/consensus/cometbft/apps/keymanager/messages.go +++ b/go/consensus/cometbft/apps/keymanager/messages.go @@ -5,9 +5,10 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" governance "github.com/oasisprotocol/oasis-core/go/governance/api" keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) func (app *keymanagerApplication) changeParameters(ctx *api.Context, msg interface{}, apply bool) (interface{}, error) { @@ -21,13 +22,13 @@ func (app *keymanagerApplication) changeParameters(ctx *api.Context, msg interfa return nil, nil } - var changes keymanager.ConsensusParameterChanges + var changes secrets.ConsensusParameterChanges if err := cbor.Unmarshal(proposal.Changes, &changes); err != nil { return nil, fmt.Errorf("keymanager: failed to unmarshal consensus parameter changes: %w", err) } // Validate changes against current parameters. - state := keymanagerState.NewMutableState(ctx.State()) + state := secretsState.NewMutableState(ctx.State()) params, err := state.ConsensusParameters(ctx) if err != nil { return nil, fmt.Errorf("keymanager: failed to load consensus parameters: %w", err) diff --git a/go/consensus/cometbft/apps/keymanager/messages_test.go b/go/consensus/cometbft/apps/keymanager/messages_test.go index 61db348e01a..389154fac79 100644 --- a/go/consensus/cometbft/apps/keymanager/messages_test.go +++ b/go/consensus/cometbft/apps/keymanager/messages_test.go @@ -8,9 +8,10 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" governance "github.com/oasisprotocol/oasis-core/go/governance/api" keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) func TestChangeParameters(t *testing.T) { @@ -20,13 +21,13 @@ func TestChangeParameters(t *testing.T) { defer ctx.Close() // Setup state. - state := keymanagerState.NewMutableState(ctx.State()) + state := secretsState.NewMutableState(ctx.State()) app := &keymanagerApplication{ state: appState, } - params := &keymanager.ConsensusParameters{ + params := &secrets.ConsensusParameters{ GasCosts: transaction.Costs{ - keymanager.GasOpUpdatePolicy: 1000, + secrets.GasOpUpdatePolicy: 1000, }, } err := state.SetConsensusParameters(ctx, params) @@ -34,9 +35,9 @@ func TestChangeParameters(t *testing.T) { // Prepare proposal. gasCosts := transaction.Costs{ - keymanager.GasOpUpdatePolicy: 2000, + secrets.GasOpUpdatePolicy: 2000, } - changes := keymanager.ConsensusParameterChanges{ + changes := secrets.ConsensusParameterChanges{ GasCosts: gasCosts, } proposal := governance.ChangeParametersProposal{ diff --git a/go/consensus/cometbft/apps/keymanager/query.go b/go/consensus/cometbft/apps/keymanager/query.go index 6134d7ea597..233f2d2ab6c 100644 --- a/go/consensus/cometbft/apps/keymanager/query.go +++ b/go/consensus/cometbft/apps/keymanager/query.go @@ -3,19 +3,14 @@ package keymanager import ( "context" - "github.com/oasisprotocol/oasis-core/go/common" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" ) // Query is the key manager query interface. type Query interface { - Status(context.Context, common.Namespace) (*keymanager.Status, error) - Statuses(context.Context) ([]*keymanager.Status, error) - MasterSecret(context.Context, common.Namespace) (*keymanager.SignedEncryptedMasterSecret, error) - EphemeralSecret(context.Context, common.Namespace) (*keymanager.SignedEncryptedEphemeralSecret, error) - Genesis(context.Context) (*keymanager.Genesis, error) + Secrets() secrets.Query } // QueryFactory is the key manager query factory. @@ -25,7 +20,7 @@ type QueryFactory struct { // QueryAt returns the key manager query interface for a specific height. func (sf *QueryFactory) QueryAt(ctx context.Context, height int64) (Query, error) { - state, err := keymanagerState.NewImmutableState(ctx, sf.state, height) + state, err := secretsState.NewImmutableState(ctx, sf.state, height) // TODO: not ok if err != nil { return nil, err } @@ -33,23 +28,11 @@ func (sf *QueryFactory) QueryAt(ctx context.Context, height int64) (Query, error } type keymanagerQuerier struct { - state *keymanagerState.ImmutableState + state *secretsState.ImmutableState } -func (kq *keymanagerQuerier) Status(ctx context.Context, id common.Namespace) (*keymanager.Status, error) { - return kq.state.Status(ctx, id) -} - -func (kq *keymanagerQuerier) Statuses(ctx context.Context) ([]*keymanager.Status, error) { - return kq.state.Statuses(ctx) -} - -func (kq *keymanagerQuerier) MasterSecret(ctx context.Context, id common.Namespace) (*keymanager.SignedEncryptedMasterSecret, error) { - return kq.state.MasterSecret(ctx, id) -} - -func (kq *keymanagerQuerier) EphemeralSecret(ctx context.Context, id common.Namespace) (*keymanager.SignedEncryptedEphemeralSecret, error) { - return kq.state.EphemeralSecret(ctx, id) +func (kq *keymanagerQuerier) Secrets() secrets.Query { + return secrets.NewQuery(kq.state) } func (app *keymanagerApplication) QueryFactory() interface{} { diff --git a/go/consensus/cometbft/apps/keymanager/secrets/ext.go b/go/consensus/cometbft/apps/keymanager/secrets/ext.go new file mode 100644 index 00000000000..ba82a471ccf --- /dev/null +++ b/go/consensus/cometbft/apps/keymanager/secrets/ext.go @@ -0,0 +1,79 @@ +package secrets + +import ( + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" + tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" +) + +// Ensure that the master and ephemeral secrets extension implements the Extension interface. +var _ tmapi.Extension = (*secretsExt)(nil) + +type secretsExt struct { + appName string + state tmapi.ApplicationState +} + +// New creates a new master and ephemeral secrets extension for the key manager application. +func New(appName string) tmapi.Extension { + return &secretsExt{ + appName: appName, + } +} + +// Methods implements api.Extension. +func (ext *secretsExt) Methods() []transaction.MethodName { + return secrets.Methods +} + +// OnRegister implements api.Extension. +func (ext *secretsExt) OnRegister(state tmapi.ApplicationState, _ tmapi.MessageDispatcher) { + ext.state = state +} + +// ExecuteTx implements api.Extension. +func (ext *secretsExt) ExecuteTx(ctx *tmapi.Context, tx *transaction.Transaction) error { + state := secretsState.NewMutableState(ctx.State()) + + switch tx.Method { + case secrets.MethodUpdatePolicy: + var sigPol secrets.SignedPolicySGX + if err := cbor.Unmarshal(tx.Body, &sigPol); err != nil { + return secrets.ErrInvalidArgument + } + return ext.updatePolicy(ctx, state, &sigPol) + case secrets.MethodPublishMasterSecret: + var sigSec secrets.SignedEncryptedMasterSecret + if err := cbor.Unmarshal(tx.Body, &sigSec); err != nil { + return secrets.ErrInvalidArgument + } + return ext.publishMasterSecret(ctx, state, &sigSec) + case secrets.MethodPublishEphemeralSecret: + var sigSec secrets.SignedEncryptedEphemeralSecret + if err := cbor.Unmarshal(tx.Body, &sigSec); err != nil { + return secrets.ErrInvalidArgument + } + return ext.publishEphemeralSecret(ctx, state, &sigSec) + default: + panic(fmt.Sprintf("keymanager: secrets: invalid method: %s", tx.Method)) + } +} + +// BeginBlock implements api.Extension. +func (ext *secretsExt) BeginBlock(ctx *tmapi.Context) error { + changed, epoch := ext.state.EpochChanged(ctx) + if !changed { + return nil + } + + return ext.onEpochChange(ctx, epoch) +} + +// EndBlock implements api.Extension. +func (ext *secretsExt) EndBlock(*tmapi.Context) error { + return nil +} diff --git a/go/consensus/cometbft/apps/keymanager/secrets/genesis.go b/go/consensus/cometbft/apps/keymanager/secrets/genesis.go new file mode 100644 index 00000000000..31466fce7b5 --- /dev/null +++ b/go/consensus/cometbft/apps/keymanager/secrets/genesis.go @@ -0,0 +1,89 @@ +package secrets + +import ( + "errors" + "fmt" + + "github.com/cometbft/cometbft/abci/types" + + "github.com/oasisprotocol/oasis-core/go/common" + tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" + genesis "github.com/oasisprotocol/oasis-core/go/genesis/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" +) + +func (ext *secretsExt) InitChain(ctx *tmapi.Context, _ types.RequestInitChain, doc *genesis.Document) error { + st := doc.KeyManager + + state := secretsState.NewMutableState(ctx.State()) + + if err := state.SetConsensusParameters(ctx, &st.Parameters); err != nil { + return fmt.Errorf("cometbft/keymanager: failed to set consensus parameters: %w", err) + } + + epoch, err := ext.state.GetCurrentEpoch(ctx) + if err != nil { + return fmt.Errorf("cometbft/keymanager: couldn't get current epoch: %w", err) + } + + // TODO: The better thing to do would be to move the registry init + // before the keymanager, and just query the registry for the runtime + // list. + regSt := doc.Registry + rtMap := make(map[common.Namespace]*registry.Runtime) + for _, rt := range regSt.Runtimes { + err := registry.VerifyRuntime(®St.Parameters, ctx.Logger(), rt, true, false, epoch) + if err != nil { + ctx.Logger().Error("InitChain: Invalid runtime", + "err", err, + ) + continue + } + + if rt.Kind == registry.KindKeyManager { + rtMap[rt.ID] = rt + } + } + + var toEmit []*secrets.Status + for i, v := range st.Statuses { + if v == nil { + return fmt.Errorf("InitChain: Status index %d is nil", i) + } + rt := rtMap[v.ID] + if rt == nil { + ctx.Logger().Error("InitChain: State for unknown key manager runtime", + "id", v.ID, + ) + continue + } + + ctx.Logger().Debug("InitChain: Registering genesis key manager", + "id", v.ID, + ) + + // Make sure the Nodes field is empty when applying genesis state. + if v.Nodes != nil { + ctx.Logger().Error("InitChain: Genesis key manager has nodes", + "id", v.ID, + ) + return errors.New("cometbft/keymanager: genesis key manager has nodes") + } + + // Set, enqueue for emit. + if err := state.SetStatus(ctx, v); err != nil { + return fmt.Errorf("cometbft/keymanager: failed to set status: %w", err) + } + toEmit = append(toEmit, v) + } + + if len(toEmit) > 0 { + ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&secrets.StatusUpdateEvent{ + Statuses: toEmit, + })) + } + + return nil +} diff --git a/go/consensus/cometbft/apps/keymanager/secrets/query.go b/go/consensus/cometbft/apps/keymanager/secrets/query.go new file mode 100644 index 00000000000..44b23572f41 --- /dev/null +++ b/go/consensus/cometbft/apps/keymanager/secrets/query.go @@ -0,0 +1,57 @@ +package secrets + +import ( + "context" + + "github.com/oasisprotocol/oasis-core/go/common" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" +) + +// Query is the key manager query interface. +type Query interface { + Status(context.Context, common.Namespace) (*secrets.Status, error) + Statuses(context.Context) ([]*secrets.Status, error) + MasterSecret(context.Context, common.Namespace) (*secrets.SignedEncryptedMasterSecret, error) + EphemeralSecret(context.Context, common.Namespace) (*secrets.SignedEncryptedEphemeralSecret, error) + Genesis(context.Context) (*secrets.Genesis, error) +} + +type querier struct { + state *secretsState.ImmutableState +} + +func (kq *querier) Status(ctx context.Context, id common.Namespace) (*secrets.Status, error) { + return kq.state.Status(ctx, id) +} + +func (kq *querier) Statuses(ctx context.Context) ([]*secrets.Status, error) { + return kq.state.Statuses(ctx) +} + +func (kq *querier) MasterSecret(ctx context.Context, id common.Namespace) (*secrets.SignedEncryptedMasterSecret, error) { + return kq.state.MasterSecret(ctx, id) +} + +func (kq *querier) EphemeralSecret(ctx context.Context, id common.Namespace) (*secrets.SignedEncryptedEphemeralSecret, error) { + return kq.state.EphemeralSecret(ctx, id) +} + +func (kq *querier) Genesis(ctx context.Context) (*secrets.Genesis, error) { + statuses, err := kq.state.Statuses(ctx) + if err != nil { + return nil, err + } + + // Remove the Nodes field of each Status. + for _, status := range statuses { + status.Nodes = nil + } + + gen := secrets.Genesis{Statuses: statuses} + return &gen, nil +} + +func NewQuery(state *secretsState.ImmutableState) Query { + return &querier{state} +} diff --git a/go/consensus/cometbft/apps/keymanager/secrets/state/interop/interop.go b/go/consensus/cometbft/apps/keymanager/secrets/state/interop/interop.go new file mode 100644 index 00000000000..280f6cdb5dc --- /dev/null +++ b/go/consensus/cometbft/apps/keymanager/secrets/state/interop/interop.go @@ -0,0 +1,162 @@ +package interop + +import ( + "context" + "crypto/sha512" + "encoding/hex" + "fmt" + + "github.com/oasisprotocol/curve25519-voi/primitives/x25519" + + beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" + memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory" + "github.com/oasisprotocol/oasis-core/go/common/sgx" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" + "github.com/oasisprotocol/oasis-core/go/storage/mkvs" +) + +// InitializeTestKeyManagerSecretsState must be kept in sync with tests in runtimes/consensus/state/keymanager.rs. +func InitializeTestKeyManagerSecretsState(ctx context.Context, mkvs mkvs.Tree) error { + state := secretsState.NewMutableState(mkvs) + + // One runtime, two key manager runtimes. + var runtime, keymanager1, keymanager2 common.Namespace + if err := runtime.UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000000"); err != nil { + return err + } + if err := keymanager1.UnmarshalHex("c000000000000000fffffffffffffffffffffffffffffffffffffffffffffffe"); err != nil { + return err + } + if err := keymanager2.UnmarshalHex("c000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff"); err != nil { + return err + } + + // Three enclave identities. + var runtimeEnclave, keymanagerEnclave1, keymanagerEnclave2 sgx.EnclaveIdentity + if err := runtimeEnclave.MrEnclave.UnmarshalHex("18256f783c071521be2da041cd9347b5bdb5a8ef58fb34658571a6e14cf1fcb0"); err != nil { + return err + } + if err := runtimeEnclave.MrSigner.UnmarshalHex("e48049d1de0eb333523991671a6c93b97dd65bcf09273d5b6bfe8262dc968ec7"); err != nil { + return err + } + if err := keymanagerEnclave1.MrEnclave.UnmarshalHex("c9a589851b1f35627177fd70378ed778170f737611e4dfbf0b6d25bdff55b474"); err != nil { + return err + } + if err := keymanagerEnclave1.MrSigner.UnmarshalHex("7d310664780931ae103ab30a90171c201af385a72757bb4683578fdebde9adf5"); err != nil { + return err + } + if err := keymanagerEnclave2.MrEnclave.UnmarshalHex("756eaf76f5482c5345808b1eaccdd5c60f864bb2aa2d2b870df00ce435af4e23"); err != nil { + return err + } + if err := keymanagerEnclave2.MrSigner.UnmarshalHex("3597a2ff0743016f28e5d7e129304ee1c43dbdae3dba94e19cee3549038a5a32"); err != nil { + return err + } + + // Signed policy. + enclavePolicySGX := secrets.EnclavePolicySGX{ + MayQuery: map[common.Namespace][]sgx.EnclaveIdentity{ + runtime: { + runtimeEnclave, + }, + }, + MayReplicate: []sgx.EnclaveIdentity{ + keymanagerEnclave2, + }, + } + policy := secrets.PolicySGX{ + Serial: 1, + ID: keymanager2, + Enclaves: map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX{ + keymanagerEnclave1: &enclavePolicySGX, + }, + MaxEphemeralSecretAge: 10, + } + sigPolicy := secrets.SignedPolicySGX{ + Policy: policy, + Signatures: []signature.Signature{}, + } + + // Two signers. + signers := []signature.Signer{ + memorySigner.NewTestSigner("first signer"), + memorySigner.NewTestSigner("second signer"), + } + + for _, signer := range signers { + sig, err := signature.Sign(signer, secrets.PolicySGXSignatureContext, cbor.Marshal(policy)) + if err != nil { + return fmt.Errorf("failed to sign policy: %w", err) + } + sigPolicy.Signatures = append(sigPolicy.Signatures, *sig) + } + + // Random checksum. + checksum, err := hex.DecodeString("1bff211fae98c88ba82388ae954b88a71d3bbe327e162e9fa711fe7a1b759c3e") + if err != nil { + return err + } + + // Add two statuses. + for _, status := range []*secrets.Status{ + { + ID: keymanager1, + IsInitialized: false, + IsSecure: false, + Checksum: nil, + Nodes: nil, + Policy: nil, + RSK: nil, + }, + { + ID: keymanager2, + IsInitialized: true, + IsSecure: true, + Checksum: checksum, + Nodes: []signature.PublicKey{ + signers[0].Public(), + signers[1].Public(), + }, + Policy: &sigPolicy, + RSK: nil, + }, + } { + if err = state.SetStatus(ctx, status); err != nil { + return fmt.Errorf("setting key manager status: %w", err) + } + } + + // Add an ephemeral secret. + rek1 := x25519.PrivateKey(sha512.Sum512_256([]byte("first rek"))) + rek2 := x25519.PrivateKey(sha512.Sum512_256([]byte("second rek"))) + epoch := 1 + secret := secrets.EncryptedEphemeralSecret{ + ID: keymanager1, + Epoch: beacon.EpochTime(epoch), + Secret: secrets.EncryptedSecret{ + Checksum: []byte{1, 2, 3, 4, 5}, + PubKey: *rek1.Public(), + Ciphertexts: map[x25519.PublicKey][]byte{ + *rek1.Public(): {1, 2, 3}, + *rek2.Public(): {4, 5, 6}, + }, + }, + } + sig, err := signature.Sign(signers[0], secrets.EncryptedEphemeralSecretSignatureContext, cbor.Marshal(secret)) + if err != nil { + return fmt.Errorf("failed to sign ephemeral secret: %w", err) + } + sigSecret := secrets.SignedEncryptedEphemeralSecret{ + Secret: secret, + Signature: sig.Signature, + } + err = state.SetEphemeralSecret(ctx, &sigSecret) + if err != nil { + return fmt.Errorf("failed to set ephemeral secret: %w", err) + } + + return nil +} diff --git a/go/consensus/cometbft/apps/keymanager/state/state.go b/go/consensus/cometbft/apps/keymanager/secrets/state/state.go similarity index 84% rename from go/consensus/cometbft/apps/keymanager/state/state.go rename to go/consensus/cometbft/apps/keymanager/secrets/state/state.go index 7f4909cad1b..968ccba7af9 100644 --- a/go/consensus/cometbft/apps/keymanager/state/state.go +++ b/go/consensus/cometbft/apps/keymanager/secrets/state/state.go @@ -8,7 +8,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/keyformat" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/storage/mkvs" ) @@ -37,7 +37,7 @@ type ImmutableState struct { } // ConsensusParameters returns the key manager consensus parameters. -func (st *ImmutableState) ConsensusParameters(ctx context.Context) (*api.ConsensusParameters, error) { +func (st *ImmutableState) ConsensusParameters(ctx context.Context) (*secrets.ConsensusParameters, error) { raw, err := st.is.Get(ctx, parametersKeyFmt.Encode()) if err != nil { return nil, abciAPI.UnavailableStateError(err) @@ -46,22 +46,22 @@ func (st *ImmutableState) ConsensusParameters(ctx context.Context) (*api.Consens return nil, fmt.Errorf("cometbft/keymanager: expected consensus parameters to be present in app state") } - var params api.ConsensusParameters + var params secrets.ConsensusParameters if err = cbor.Unmarshal(raw, ¶ms); err != nil { return nil, abciAPI.UnavailableStateError(err) } return ¶ms, nil } -func (st *ImmutableState) Statuses(ctx context.Context) ([]*api.Status, error) { +func (st *ImmutableState) Statuses(ctx context.Context) ([]*secrets.Status, error) { rawStatuses, err := st.getStatusesRaw(ctx) if err != nil { return nil, err } - var statuses []*api.Status + var statuses []*secrets.Status for _, raw := range rawStatuses { - var status api.Status + var status secrets.Status if err = cbor.Unmarshal(raw, &status); err != nil { return nil, abciAPI.UnavailableStateError(err) } @@ -88,48 +88,48 @@ func (st *ImmutableState) getStatusesRaw(ctx context.Context) ([][]byte, error) return rawVec, nil } -func (st *ImmutableState) Status(ctx context.Context, id common.Namespace) (*api.Status, error) { +func (st *ImmutableState) Status(ctx context.Context, id common.Namespace) (*secrets.Status, error) { data, err := st.is.Get(ctx, statusKeyFmt.Encode(&id)) if err != nil { return nil, abciAPI.UnavailableStateError(err) } if data == nil { - return nil, api.ErrNoSuchStatus + return nil, secrets.ErrNoSuchStatus } - var status api.Status + var status secrets.Status if err := cbor.Unmarshal(data, &status); err != nil { return nil, abciAPI.UnavailableStateError(err) } return &status, nil } -func (st *ImmutableState) MasterSecret(ctx context.Context, id common.Namespace) (*api.SignedEncryptedMasterSecret, error) { +func (st *ImmutableState) MasterSecret(ctx context.Context, id common.Namespace) (*secrets.SignedEncryptedMasterSecret, error) { data, err := st.is.Get(ctx, masterSecretKeyFmt.Encode(&id)) if err != nil { return nil, abciAPI.UnavailableStateError(err) } if data == nil { - return nil, api.ErrNoSuchMasterSecret + return nil, secrets.ErrNoSuchMasterSecret } - var secret api.SignedEncryptedMasterSecret + var secret secrets.SignedEncryptedMasterSecret if err := cbor.Unmarshal(data, &secret); err != nil { return nil, abciAPI.UnavailableStateError(err) } return &secret, nil } -func (st *ImmutableState) EphemeralSecret(ctx context.Context, id common.Namespace) (*api.SignedEncryptedEphemeralSecret, error) { +func (st *ImmutableState) EphemeralSecret(ctx context.Context, id common.Namespace) (*secrets.SignedEncryptedEphemeralSecret, error) { data, err := st.is.Get(ctx, ephemeralSecretKeyFmt.Encode(&id)) if err != nil { return nil, abciAPI.UnavailableStateError(err) } if data == nil { - return nil, api.ErrNoSuchEphemeralSecret + return nil, secrets.ErrNoSuchEphemeralSecret } - var secret api.SignedEncryptedEphemeralSecret + var secret secrets.SignedEncryptedEphemeralSecret if err := cbor.Unmarshal(data, &secret); err != nil { return nil, abciAPI.UnavailableStateError(err) } @@ -154,7 +154,7 @@ type MutableState struct { // SetConsensusParameters sets key manager consensus parameters. // // NOTE: This method must only be called from InitChain/EndBlock contexts. -func (st *MutableState) SetConsensusParameters(ctx context.Context, params *api.ConsensusParameters) error { +func (st *MutableState) SetConsensusParameters(ctx context.Context, params *secrets.ConsensusParameters) error { if err := st.is.CheckContextMode(ctx, []abciAPI.ContextMode{abciAPI.ContextInitChain, abciAPI.ContextEndBlock}); err != nil { return err } @@ -162,17 +162,17 @@ func (st *MutableState) SetConsensusParameters(ctx context.Context, params *api. return abciAPI.UnavailableStateError(err) } -func (st *MutableState) SetStatus(ctx context.Context, status *api.Status) error { +func (st *MutableState) SetStatus(ctx context.Context, status *secrets.Status) error { err := st.ms.Insert(ctx, statusKeyFmt.Encode(&status.ID), cbor.Marshal(status)) return abciAPI.UnavailableStateError(err) } -func (st *MutableState) SetMasterSecret(ctx context.Context, secret *api.SignedEncryptedMasterSecret) error { +func (st *MutableState) SetMasterSecret(ctx context.Context, secret *secrets.SignedEncryptedMasterSecret) error { err := st.ms.Insert(ctx, masterSecretKeyFmt.Encode(&secret.Secret.ID), cbor.Marshal(secret)) return abciAPI.UnavailableStateError(err) } -func (st *MutableState) SetEphemeralSecret(ctx context.Context, secret *api.SignedEncryptedEphemeralSecret) error { +func (st *MutableState) SetEphemeralSecret(ctx context.Context, secret *secrets.SignedEncryptedEphemeralSecret) error { err := st.ms.Insert(ctx, ephemeralSecretKeyFmt.Encode(&secret.Secret.ID), cbor.Marshal(secret)) return abciAPI.UnavailableStateError(err) } diff --git a/go/consensus/cometbft/apps/keymanager/state/state_test.go b/go/consensus/cometbft/apps/keymanager/secrets/state/state_test.go similarity index 65% rename from go/consensus/cometbft/apps/keymanager/state/state_test.go rename to go/consensus/cometbft/apps/keymanager/secrets/state/state_test.go index 96ec010f36b..a15834b736d 100644 --- a/go/consensus/cometbft/apps/keymanager/state/state_test.go +++ b/go/consensus/cometbft/apps/keymanager/secrets/state/state_test.go @@ -8,7 +8,7 @@ import ( beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) func TestMasterSecret(t *testing.T) { @@ -25,19 +25,19 @@ func TestMasterSecret(t *testing.T) { common.NewTestNamespaceFromSeed([]byte("runtime 1"), common.NamespaceKeyManager), common.NewTestNamespaceFromSeed([]byte("runtime 2"), common.NamespaceKeyManager), } - secrets := make([]*api.SignedEncryptedMasterSecret, 0, 10) - for i := 0; i < cap(secrets); i++ { - secret := api.SignedEncryptedMasterSecret{ - Secret: api.EncryptedMasterSecret{ + masterSecrets := make([]*secrets.SignedEncryptedMasterSecret, 0, 10) + for i := 0; i < cap(masterSecrets); i++ { + secret := secrets.SignedEncryptedMasterSecret{ + Secret: secrets.EncryptedMasterSecret{ ID: runtimes[i%2], Generation: uint64(i), }, } - secrets = append(secrets, &secret) + masterSecrets = append(masterSecrets, &secret) } // Test adding secrets. - for _, secret := range secrets { + for _, secret := range masterSecrets { err := s.SetMasterSecret(ctx, secret) require.NoError(err, "SetMasterSecret()") } @@ -46,10 +46,10 @@ func TestMasterSecret(t *testing.T) { for i, runtime := range runtimes { secret, err := s.MasterSecret(ctx, runtime) require.NoError(err, "MasterSecret()") - require.Equal(secrets[8+i], secret, "last master secret should be kept") + require.Equal(masterSecrets[8+i], secret, "last master secret should be kept") } _, err := s.MasterSecret(ctx, common.Namespace{1, 2, 3}) - require.EqualError(err, api.ErrNoSuchMasterSecret.Error(), "MasterSecret should error for non-existing secrets") + require.EqualError(err, secrets.ErrNoSuchMasterSecret.Error(), "MasterSecret should error for non-existing secrets") } func TestEphemeralSecret(t *testing.T) { @@ -66,19 +66,19 @@ func TestEphemeralSecret(t *testing.T) { common.NewTestNamespaceFromSeed([]byte("runtime 1"), common.NamespaceKeyManager), common.NewTestNamespaceFromSeed([]byte("runtime 2"), common.NamespaceKeyManager), } - secrets := make([]*api.SignedEncryptedEphemeralSecret, 0, 10) - for i := 0; i < cap(secrets); i++ { - secret := api.SignedEncryptedEphemeralSecret{ - Secret: api.EncryptedEphemeralSecret{ + masterSecrets := make([]*secrets.SignedEncryptedEphemeralSecret, 0, 10) + for i := 0; i < cap(masterSecrets); i++ { + secret := secrets.SignedEncryptedEphemeralSecret{ + Secret: secrets.EncryptedEphemeralSecret{ ID: runtimes[i%2], Epoch: beacon.EpochTime(i), }, } - secrets = append(secrets, &secret) + masterSecrets = append(masterSecrets, &secret) } // Test adding secrets. - for _, secret := range secrets { + for _, secret := range masterSecrets { err := s.SetEphemeralSecret(ctx, secret) require.NoError(err, "SetEphemeralSecret()") } @@ -87,8 +87,8 @@ func TestEphemeralSecret(t *testing.T) { for i, runtime := range runtimes { secret, err := s.EphemeralSecret(ctx, runtime) require.NoError(err, "EphemeralSecret()") - require.Equal(secrets[8+i], secret, "last ephemeral secret should be kept") + require.Equal(masterSecrets[8+i], secret, "last ephemeral secret should be kept") } _, err := s.EphemeralSecret(ctx, common.Namespace{1, 2, 3}) - require.EqualError(err, api.ErrNoSuchEphemeralSecret.Error(), "EphemeralSecret should error for non-existing secrets") + require.EqualError(err, secrets.ErrNoSuchEphemeralSecret.Error(), "EphemeralSecret should error for non-existing secrets") } diff --git a/go/consensus/cometbft/apps/keymanager/secrets/status.go b/go/consensus/cometbft/apps/keymanager/secrets/status.go new file mode 100644 index 00000000000..b7a1df7216c --- /dev/null +++ b/go/consensus/cometbft/apps/keymanager/secrets/status.go @@ -0,0 +1,345 @@ +package secrets + +import ( + "bytes" + "encoding/hex" + "fmt" + "time" + + "golang.org/x/crypto/sha3" + + beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" + "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-core/go/common/node" + tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" + registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" + "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" +) + +// minProposalReplicationPercent is the minimum percentage of enclaves in the key manager committee +// that must replicate the proposal for the next master secret before it is accepted. +const minProposalReplicationPercent = 66 + +var emptyHashSha3 = sha3.Sum256(nil) + +func (ext *secretsExt) onEpochChange(ctx *tmapi.Context, epoch beacon.EpochTime) error { + // Query the runtime and node lists. + regState := registryState.NewMutableState(ctx.State()) + runtimes, _ := regState.Runtimes(ctx) + nodes, _ := regState.Nodes(ctx) + registry.SortNodeList(nodes) + + params, err := regState.ConsensusParameters(ctx) + if err != nil { + return fmt.Errorf("failed to get consensus parameters: %w", err) + } + + // Recalculate all the key manager statuses. + // + // Note: This assumes that once a runtime is registered, it never expires. + var toEmit []*secrets.Status + state := secretsState.NewMutableState(ctx.State()) + for _, rt := range runtimes { + if rt.Kind != registry.KindKeyManager { + continue + } + + var forceEmit bool + oldStatus, err := state.Status(ctx, rt.ID) + switch err { + case nil: + case secrets.ErrNoSuchStatus: + // This must be a new key manager runtime. + forceEmit = true + oldStatus = &secrets.Status{ + ID: rt.ID, + } + default: + // This is fatal, as it suggests state corruption. + ctx.Logger().Error("failed to query key manager status", + "id", rt.ID, + "err", err, + ) + return fmt.Errorf("failed to query key manager status: %w", err) + } + + secret, err := state.MasterSecret(ctx, rt.ID) + if err != nil && err != secrets.ErrNoSuchMasterSecret { + ctx.Logger().Error("failed to query key manager master secret", + "id", rt.ID, + "err", err, + ) + return fmt.Errorf("failed to query key manager master secret: %w", err) + } + + newStatus := generateStatus(ctx, rt, oldStatus, secret, nodes, params, epoch) + if forceEmit || !bytes.Equal(cbor.Marshal(oldStatus), cbor.Marshal(newStatus)) { + ctx.Logger().Debug("status updated", + "id", newStatus.ID, + "is_initialized", newStatus.IsInitialized, + "is_secure", newStatus.IsSecure, + "generation", newStatus.Generation, + "rotation_epoch", newStatus.RotationEpoch, + "checksum", hex.EncodeToString(newStatus.Checksum), + "rsk", newStatus.RSK, + "nodes", newStatus.Nodes, + ) + + // Set, enqueue for emit. + if err = state.SetStatus(ctx, newStatus); err != nil { + return fmt.Errorf("failed to set key manager status: %w", err) + } + toEmit = append(toEmit, newStatus) + } + } + + // Note: It may be a good idea to sweep statuses that don't have runtimes, + // but as runtime registrations last forever, so this shouldn't be possible. + + // Emit the update event if required. + if len(toEmit) > 0 { + ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&secrets.StatusUpdateEvent{ + Statuses: toEmit, + })) + } + + return nil +} + +func generateStatus( // nolint: gocyclo + ctx *tmapi.Context, + kmrt *registry.Runtime, + oldStatus *secrets.Status, + secret *secrets.SignedEncryptedMasterSecret, + nodes []*node.Node, + params *registry.ConsensusParameters, + epoch beacon.EpochTime, +) *secrets.Status { + status := &secrets.Status{ + ID: kmrt.ID, + IsInitialized: oldStatus.IsInitialized, + IsSecure: oldStatus.IsSecure, + Generation: oldStatus.Generation, + RotationEpoch: oldStatus.RotationEpoch, + Checksum: oldStatus.Checksum, + Policy: oldStatus.Policy, + } + + // Data needed to count the nodes that have replicated the proposal for the next master secret. + var ( + nextGeneration uint64 + nextChecksum []byte + nextRSK *signature.PublicKey + updatedNodes []signature.PublicKey + ) + nextGeneration = status.NextGeneration() + if secret != nil && secret.Secret.Generation == nextGeneration && secret.Secret.Epoch == epoch { + nextChecksum = secret.Secret.Secret.Checksum + } + + // Compute the policy hash to reject nodes that are not up-to-date. + var rawPolicy []byte + if status.Policy != nil { + rawPolicy = cbor.Marshal(status.Policy) + } + policyHash := sha3.Sum256(rawPolicy) + + ts := ctx.Now() + height := uint64(ctx.BlockHeight()) + + // Construct a key manager committee. A node is added to the committee if it supports + // at least one version of the key manager runtime and if all supported versions conform + // to the key manager status fields. +nextNode: + for _, n := range nodes { + if n.IsExpired(uint64(epoch)) { + continue + } + if !n.HasRoles(node.RoleKeyManager) { + continue + } + + secretReplicated := true + isInitialized := status.IsInitialized + isSecure := status.IsSecure + RSK := status.RSK + nRSK := nextRSK + + var numVersions int + for _, nodeRt := range n.Runtimes { + if !nodeRt.ID.Equal(&kmrt.ID) { + continue + } + + vars := []interface{}{ + "id", kmrt.ID, + "node_id", n.ID, + "version", nodeRt.Version, + } + + var teeOk bool + if nodeRt.Capabilities.TEE == nil { + teeOk = kmrt.TEEHardware == node.TEEHardwareInvalid + } else { + teeOk = kmrt.TEEHardware == nodeRt.Capabilities.TEE.Hardware + } + if !teeOk { + ctx.Logger().Error("TEE hardware mismatch", vars...) + continue nextNode + } + + initResponse, err := VerifyExtraInfo(ctx.Logger(), n.ID, kmrt, nodeRt, ts, height, params) + if err != nil { + ctx.Logger().Error("failed to validate ExtraInfo", append(vars, "err", err)...) + continue nextNode + } + + // Skip nodes with mismatched policy. + var nodePolicyHash [secrets.ChecksumSize]byte + switch len(initResponse.PolicyChecksum) { + case 0: + nodePolicyHash = emptyHashSha3 + case secrets.ChecksumSize: + copy(nodePolicyHash[:], initResponse.PolicyChecksum) + default: + ctx.Logger().Error("failed to parse policy checksum", append(vars, "err", err)...) + continue nextNode + } + if policyHash != nodePolicyHash { + ctx.Logger().Error("Policy checksum mismatch for runtime", vars...) + continue nextNode + } + + // Set immutable status fields that cannot change after initialization. + if !isInitialized { + // The first version gets to be the source of truth. + isInitialized = true + isSecure = initResponse.IsSecure + } + + // Skip nodes with mismatched status fields. + if initResponse.IsSecure != isSecure { + ctx.Logger().Error("Security status mismatch for runtime", vars...) + continue nextNode + } + + // Skip nodes with mismatched checksum. + // Note that a node needs to register with an empty checksum if no master secrets + // have been generated so far. Otherwise, if secrets have been generated, the node + // needs to register with a checksum computed over all the secrets generated so far + // since the key manager's checksum is updated after every master secret rotation. + if !bytes.Equal(initResponse.Checksum, status.Checksum) { + ctx.Logger().Error("Checksum mismatch for runtime", vars...) + continue nextNode + } + + // Update mutable status fields that can change on epoch transitions. + if RSK == nil { + // The first version with non-nil runtime signing key gets to be the source of truth. + RSK = initResponse.RSK + } + + // Skip nodes with mismatched runtime signing key. + // For backward compatibility we always allow nodes without runtime signing key. + if initResponse.RSK != nil && !initResponse.RSK.Equal(*RSK) { + ctx.Logger().Error("Runtime signing key mismatch for runtime", vars) + continue nextNode + } + + // Check if all versions have replicated the last master secret, + // derived the same RSK and are ready to move to the next generation. + if !bytes.Equal(initResponse.NextChecksum, nextChecksum) { + secretReplicated = false + } + if nRSK == nil { + nRSK = initResponse.NextRSK + } + if initResponse.NextRSK != nil && !initResponse.NextRSK.Equal(*nRSK) { + secretReplicated = false + } + + numVersions++ + } + + if numVersions == 0 { + continue + } + if !isInitialized { + panic("the key manager must be initialized") + } + if secretReplicated { + nextRSK = nRSK + updatedNodes = append(updatedNodes, n.ID) + } + + // If the key manager is not initialized, the first verified node gets to be the source + // of truth, every other node will sync off it. + if !status.IsInitialized { + status.IsInitialized = true + status.IsSecure = isSecure + } + status.RSK = RSK + status.Nodes = append(status.Nodes, n.ID) + } + + // Accept the proposal if the majority of the nodes have replicated + // the proposal for the next master secret. + if numNodes := len(status.Nodes); numNodes > 0 && nextChecksum != nil { + percent := len(updatedNodes) * 100 / numNodes + if percent >= minProposalReplicationPercent { + status.Generation = nextGeneration + status.RotationEpoch = epoch + status.Checksum = nextChecksum + status.RSK = nextRSK + status.Nodes = updatedNodes + } + } + + return status +} + +// VerifyExtraInfo verifies and parses the per-node + per-runtime ExtraInfo +// blob for a key manager. +func VerifyExtraInfo( + logger *logging.Logger, + nodeID signature.PublicKey, + rt *registry.Runtime, + nodeRt *node.Runtime, + ts time.Time, + height uint64, + params *registry.ConsensusParameters, +) (*secrets.InitResponse, error) { + var ( + hw node.TEEHardware + rak signature.PublicKey + ) + if nodeRt.Capabilities.TEE == nil || nodeRt.Capabilities.TEE.Hardware == node.TEEHardwareInvalid { + hw = node.TEEHardwareInvalid + rak = api.InsecureRAK + } else { + hw = nodeRt.Capabilities.TEE.Hardware + rak = nodeRt.Capabilities.TEE.RAK + } + if hw != rt.TEEHardware { + return nil, fmt.Errorf("keymanager: TEEHardware mismatch") + } else if err := registry.VerifyNodeRuntimeEnclaveIDs(logger, nodeID, nodeRt, rt, params.TEEFeatures, ts, height); err != nil { + return nil, err + } + if nodeRt.ExtraInfo == nil { + return nil, fmt.Errorf("keymanager: missing ExtraInfo") + } + + var untrustedSignedInitResponse secrets.SignedInitResponse + if err := cbor.Unmarshal(nodeRt.ExtraInfo, &untrustedSignedInitResponse); err != nil { + return nil, err + } + if err := untrustedSignedInitResponse.Verify(rak); err != nil { + return nil, err + } + return &untrustedSignedInitResponse.InitResponse, nil +} diff --git a/go/consensus/cometbft/apps/keymanager/keymanager_test.go b/go/consensus/cometbft/apps/keymanager/secrets/status_test.go similarity index 85% rename from go/consensus/cometbft/apps/keymanager/keymanager_test.go rename to go/consensus/cometbft/apps/keymanager/secrets/status_test.go index dba2ff30ec2..bbcc8267bf3 100644 --- a/go/consensus/cometbft/apps/keymanager/keymanager_test.go +++ b/go/consensus/cometbft/apps/keymanager/secrets/status_test.go @@ -1,4 +1,4 @@ -package keymanager +package secrets import ( "testing" @@ -15,6 +15,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/version" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registry "github.com/oasisprotocol/oasis-core/go/registry/api" ) @@ -24,15 +25,10 @@ func TestGenerateStatus(t *testing.T) { ctx := appState.NewContext(abciAPI.ContextEndBlock) defer ctx.Close() - // Prepare app. - app := &keymanagerApplication{ - state: appState, - } - // Prepare vars. params := ®istry.ConsensusParameters{} - policy := api.SignedPolicySGX{ - Policy: api.PolicySGX{ + policy := secrets.SignedPolicySGX{ + Policy: secrets.PolicySGX{ Serial: 1, }, } @@ -42,20 +38,20 @@ func TestGenerateStatus(t *testing.T) { // Prepare two responses so that we can test nodes running different versions. rakSigner := api.TestSigners[0] - initResponse := api.InitResponse{ + initResponse := secrets.InitResponse{ IsSecure: true, Checksum: checksum, PolicyChecksum: policyChecksum[:], } - sigInitResponse, err := api.SignInitResponse(rakSigner, &initResponse) + sigInitResponse, err := secrets.SignInitResponse(rakSigner, &initResponse) require.NoError(t, err, "SignInitResponse") initResponse.Checksum = nil - sigInitResponseSecure, err := api.SignInitResponse(rakSigner, &initResponse) + sigInitResponseSecure, err := secrets.SignInitResponse(rakSigner, &initResponse) require.NoError(t, err, "SignInitResponse") initResponse.IsSecure = false - sigInitResponseInsecure, err := api.SignInitResponse(rakSigner, &initResponse) + sigInitResponseInsecure, err := secrets.SignInitResponse(rakSigner, &initResponse) require.NoError(t, err, "SignInitResponse") // Two key manager runtimes, one compute runtime. @@ -65,14 +61,14 @@ func TestGenerateStatus(t *testing.T) { require.NoError(t, runtimeIDs[2].UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000002"), "runtime 2") // Initial key manager statuses. - initializedStatus := &api.Status{ + initializedStatus := &secrets.Status{ ID: runtimeIDs[0], IsInitialized: true, IsSecure: true, Checksum: checksum, Policy: &policy, } - uninitializedStatus := &api.Status{ + uninitializedStatus := &secrets.Status{ ID: runtimeIDs[0], Policy: &policy, } @@ -211,10 +207,10 @@ func TestGenerateStatus(t *testing.T) { t.Run("No nodes", func(t *testing.T) { require := require.New(t) - newStatus := app.generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes[0:6], params, epoch) + newStatus := generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes[0:6], params, epoch) require.Equal(uninitializedStatus, newStatus, "key manager committee should be empty") - newStatus = app.generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes[0:6], params, epoch) + newStatus = generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes[0:6], params, epoch) require.Equal(initializedStatus, newStatus, "key manager committee should be empty") }) @@ -222,23 +218,23 @@ func TestGenerateStatus(t *testing.T) { require := require.New(t) // Node 6 (secure = false) - expStatus := &api.Status{ + expStatus := &secrets.Status{ ID: runtimeIDs[0], IsInitialized: true, IsSecure: false, Policy: &policy, Nodes: []signature.PublicKey{nodes[6].ID}, } - newStatus := app.generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes[6:7], params, epoch) + newStatus := generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes[6:7], params, epoch) require.Equal(expStatus, newStatus, "node 6 should form the committee if key manager not initialized") - newStatus = app.generateStatus(ctx, runtimes[0], expStatus, nil, nodes[6:7], params, epoch) + newStatus = generateStatus(ctx, runtimes[0], expStatus, nil, nodes[6:7], params, epoch) require.Equal(expStatus, newStatus, "node 6 should form the committee if key manager is not secure") expStatus.IsSecure = true expStatus.Checksum = checksum expStatus.Nodes = nil - newStatus = app.generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes[6:7], params, epoch) + newStatus = generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes[6:7], params, epoch) require.Equal(expStatus, newStatus, "node 6 should not be added to the committee if key manager is secure or checksum differs") }) @@ -248,31 +244,31 @@ func TestGenerateStatus(t *testing.T) { // The first node is the source of truth when constructing a committee. // If the node 6 is processed before node 7, the latter won't be accepted as it is secure. // Nodes 8 and 9 cannot be a part of the committee as their checksum differs. - expStatus := &api.Status{ + expStatus := &secrets.Status{ ID: runtimeIDs[0], IsInitialized: true, IsSecure: false, Policy: &policy, Nodes: []signature.PublicKey{nodes[6].ID}, } - newStatus := app.generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes, params, epoch) + newStatus := generateStatus(ctx, runtimes[0], uninitializedStatus, nil, nodes, params, epoch) require.Equal(expStatus, newStatus, "node 6 should be the source of truth and form the committee") // If the order is reversed, it should be the other way around. expStatus.IsSecure = true expStatus.Nodes = []signature.PublicKey{nodes[7].ID} - newStatus = app.generateStatus(ctx, runtimes[0], uninitializedStatus, nil, reverse(nodes), params, epoch) + newStatus = generateStatus(ctx, runtimes[0], uninitializedStatus, nil, reverse(nodes), params, epoch) require.Equal(expStatus, newStatus, "node 7 should be the source of truth and form the committee") // If the key manager is already initialized as secure with a checksum, then all nodes // except 8 and 9 are ignored. expStatus.Checksum = checksum expStatus.Nodes = []signature.PublicKey{nodes[8].ID, nodes[9].ID} - newStatus = app.generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes, params, epoch) + newStatus = generateStatus(ctx, runtimes[0], initializedStatus, nil, nodes, params, epoch) require.Equal(expStatus, newStatus, "node 7 and 8 should form the committee if key manager is initialized as secure") // The second key manager. - expStatus = &api.Status{ + expStatus = &secrets.Status{ ID: runtimeIDs[1], IsInitialized: true, IsSecure: true, @@ -281,7 +277,7 @@ func TestGenerateStatus(t *testing.T) { Nodes: []signature.PublicKey{nodes[4].ID, nodes[9].ID}, } initializedStatus.ID = runtimeIDs[1] - newStatus = app.generateStatus(ctx, runtimes[1], initializedStatus, nil, nodes, params, epoch) + newStatus = generateStatus(ctx, runtimes[1], initializedStatus, nil, nodes, params, epoch) require.Equal(expStatus, newStatus, "node 4 and 9 should form the committee") }) } diff --git a/go/consensus/cometbft/apps/keymanager/transactions.go b/go/consensus/cometbft/apps/keymanager/secrets/txs.go similarity index 86% rename from go/consensus/cometbft/apps/keymanager/transactions.go rename to go/consensus/cometbft/apps/keymanager/secrets/txs.go index ec45f09cf93..e707357bdb4 100644 --- a/go/consensus/cometbft/apps/keymanager/transactions.go +++ b/go/consensus/cometbft/apps/keymanager/secrets/txs.go @@ -1,4 +1,4 @@ -package keymanager +package secrets import ( "fmt" @@ -10,16 +10,17 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/node" tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registry "github.com/oasisprotocol/oasis-core/go/registry/api" ) -func (app *keymanagerApplication) updatePolicy( +func (ext *secretsExt) updatePolicy( ctx *tmapi.Context, - state *keymanagerState.MutableState, - sigPol *api.SignedPolicySGX, + state *secretsState.MutableState, + sigPol *secrets.SignedPolicySGX, ) error { // Ensure that the runtime exists and is a key manager. regState := registryState.NewMutableState(ctx.State()) @@ -37,9 +38,9 @@ func (app *keymanagerApplication) updatePolicy( oldStatus, err := state.Status(ctx, kmRt.ID) switch err { case nil: - case api.ErrNoSuchStatus: + case secrets.ErrNoSuchStatus: // This must be a new key manager runtime. - oldStatus = &api.Status{ + oldStatus = &secrets.Status{ ID: kmRt.ID, } default: @@ -47,7 +48,7 @@ func (app *keymanagerApplication) updatePolicy( } // Validate the tx. - if err = api.SanityCheckSignedPolicySGX(oldStatus.Policy, sigPol); err != nil { + if err = secrets.SanityCheckSignedPolicySGX(oldStatus.Policy, sigPol); err != nil { return err } @@ -60,7 +61,7 @@ func (app *keymanagerApplication) updatePolicy( if err != nil { return err } - if err = ctx.Gas().UseGas(1, api.GasOpUpdatePolicy, kmParams.GasCosts); err != nil { + if err = ctx.Gas().UseGas(1, secrets.GasOpUpdatePolicy, kmParams.GasCosts); err != nil { return err } @@ -80,7 +81,7 @@ func (app *keymanagerApplication) updatePolicy( // TODO: It would be possible to update the cohort on each // node-reregistration, but I'm not sure how often the policy // will get updated. - epoch, err := app.state.GetCurrentEpoch(ctx) + epoch, err := ext.state.GetCurrentEpoch(ctx) if err != nil { return err } @@ -93,7 +94,7 @@ func (app *keymanagerApplication) updatePolicy( nodes, _ := regState.Nodes(ctx) registry.SortNodeList(nodes) oldStatus.Policy = sigPol - newStatus := app.generateStatus(ctx, kmRt, oldStatus, nil, nodes, regParams, epoch) + newStatus := generateStatus(ctx, kmRt, oldStatus, nil, nodes, regParams, epoch) if err := state.SetStatus(ctx, newStatus); err != nil { ctx.Logger().Error("keymanager: failed to set key manager status", "err", err, @@ -101,8 +102,8 @@ func (app *keymanagerApplication) updatePolicy( return fmt.Errorf("keymanager: failed to set key manager status: %w", err) } - ctx.EmitEvent(tmapi.NewEventBuilder(app.Name()).TypedAttribute(&api.StatusUpdateEvent{ - Statuses: []*api.Status{newStatus}, + ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&secrets.StatusUpdateEvent{ + Statuses: []*secrets.Status{newStatus}, })) return nil @@ -127,10 +128,10 @@ func (app *keymanagerApplication) updatePolicy( // It's worth noting that the process of generating, publishing, and replicating master // secrets differs from that of ephemeral secrets. For more information, please refer // to the description of the publishEphemeralSecret function. -func (app *keymanagerApplication) publishMasterSecret( +func (ext *secretsExt) publishMasterSecret( ctx *tmapi.Context, - state *keymanagerState.MutableState, - secret *api.SignedEncryptedMasterSecret, + state *secretsState.MutableState, + secret *secrets.SignedEncryptedMasterSecret, ) error { // Ensure that the runtime exists and is a key manager. regState := registryState.NewMutableState(ctx.State()) @@ -150,7 +151,7 @@ func (app *keymanagerApplication) publishMasterSecret( // Reject if the master secret has been proposed in this epoch. lastSecret, err := state.MasterSecret(ctx, secret.Secret.ID) - if err != nil && err != api.ErrNoSuchMasterSecret { + if err != nil && err != secrets.ErrNoSuchMasterSecret { return err } if lastSecret != nil && secret.Secret.Epoch == lastSecret.Secret.Epoch { @@ -165,7 +166,7 @@ func (app *keymanagerApplication) publishMasterSecret( // Verify the secret. Master secrets can be published for the next epoch and for // the next generation only. nextGen := kmStatus.NextGeneration() - epoch, err := app.state.GetCurrentEpoch(ctx) + epoch, err := ext.state.GetCurrentEpoch(ctx) if err != nil { return err } @@ -190,7 +191,7 @@ func (app *keymanagerApplication) publishMasterSecret( if err != nil { return err } - if err = ctx.Gas().UseGas(1, api.GasOpPublishMasterSecret, kmParams.GasCosts); err != nil { + if err = ctx.Gas().UseGas(1, secrets.GasOpPublishMasterSecret, kmParams.GasCosts); err != nil { return err } @@ -207,7 +208,7 @@ func (app *keymanagerApplication) publishMasterSecret( return fmt.Errorf("keymanager: failed to set key manager master secret: %w", err) } - ctx.EmitEvent(tmapi.NewEventBuilder(app.Name()).TypedAttribute(&api.MasterSecretPublishedEvent{ + ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&secrets.MasterSecretPublishedEvent{ Secret: secret, })) @@ -227,10 +228,10 @@ func (app *keymanagerApplication) publishMasterSecret( // // Note that ephemeral secrets differ from master secrets. For more information, see // the description of the publishMasterSecret function. -func (app *keymanagerApplication) publishEphemeralSecret( +func (ext *secretsExt) publishEphemeralSecret( ctx *tmapi.Context, - state *keymanagerState.MutableState, - secret *api.SignedEncryptedEphemeralSecret, + state *secretsState.MutableState, + secret *secrets.SignedEncryptedEphemeralSecret, ) error { // Ensure that the runtime exists and is a key manager. regState := registryState.NewMutableState(ctx.State()) @@ -250,7 +251,7 @@ func (app *keymanagerApplication) publishEphemeralSecret( // Reject if the ephemeral secret has been published in this epoch. lastSecret, err := state.EphemeralSecret(ctx, secret.Secret.ID) - if err != nil && err != api.ErrNoSuchEphemeralSecret { + if err != nil && err != secrets.ErrNoSuchEphemeralSecret { return err } if lastSecret != nil && secret.Secret.Epoch == lastSecret.Secret.Epoch { @@ -258,7 +259,7 @@ func (app *keymanagerApplication) publishEphemeralSecret( } // Verify the secret. Ephemeral secrets can be published for the next epoch only. - epoch, err := app.state.GetCurrentEpoch(ctx) + epoch, err := ext.state.GetCurrentEpoch(ctx) if err != nil { return err } @@ -283,7 +284,7 @@ func (app *keymanagerApplication) publishEphemeralSecret( if err != nil { return err } - if err = ctx.Gas().UseGas(1, api.GasOpPublishEphemeralSecret, kmParams.GasCosts); err != nil { + if err = ctx.Gas().UseGas(1, secrets.GasOpPublishEphemeralSecret, kmParams.GasCosts); err != nil { return err } @@ -300,7 +301,7 @@ func (app *keymanagerApplication) publishEphemeralSecret( return fmt.Errorf("keymanager: failed to set key manager ephemeral secret: %w", err) } - ctx.EmitEvent(tmapi.NewEventBuilder(app.Name()).TypedAttribute(&api.EphemeralSecretPublishedEvent{ + ctx.EmitEvent(tmapi.NewEventBuilder(ext.appName).TypedAttribute(&secrets.EphemeralSecretPublishedEvent{ Secret: secret, })) @@ -353,7 +354,7 @@ func runtimeAttestationKey(ctx *tmapi.Context, regState *registryState.MutableSt return rak, nil } -func runtimeEncryptionKeys(ctx *tmapi.Context, regState *registryState.MutableState, kmRt *registry.Runtime, kmStatus *api.Status) map[x25519.PublicKey]struct{} { +func runtimeEncryptionKeys(ctx *tmapi.Context, regState *registryState.MutableState, kmRt *registry.Runtime, kmStatus *secrets.Status) map[x25519.PublicKey]struct{} { // Fetch REKs of the key manager committee. reks := make(map[x25519.PublicKey]struct{}) for _, id := range kmStatus.Nodes { diff --git a/go/consensus/cometbft/apps/keymanager/transactions_test.go b/go/consensus/cometbft/apps/keymanager/secrets/txs_test.go similarity index 84% rename from go/consensus/cometbft/apps/keymanager/transactions_test.go rename to go/consensus/cometbft/apps/keymanager/secrets/txs_test.go index 881fee282ac..270a2d7d2fd 100644 --- a/go/consensus/cometbft/apps/keymanager/transactions_test.go +++ b/go/consensus/cometbft/apps/keymanager/secrets/txs_test.go @@ -1,4 +1,4 @@ -package keymanager +package secrets import ( "crypto/sha512" @@ -16,9 +16,9 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/entity" "github.com/oasisprotocol/oasis-core/go/common/node" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" - "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registryAPI "github.com/oasisprotocol/oasis-core/go/registry/api" ) @@ -26,7 +26,9 @@ func TestPublishEphemeralSecret(t *testing.T) { // Prepare key manager app. cfg := abciAPI.MockApplicationStateConfig{} appState := abciAPI.NewMockApplicationState(&cfg) - app := keymanagerApplication{appState} + ext := secretsExt{ + state: appState, + } // Prepare abci contexts. ctx := appState.NewContext(abciAPI.ContextEndBlock) @@ -35,11 +37,11 @@ func TestPublishEphemeralSecret(t *testing.T) { defer txCtx.Close() // Prepare states. - kmState := keymanagerState.NewMutableState(ctx.State()) + kmState := secretsState.NewMutableState(ctx.State()) regState := registryState.NewMutableState(ctx.State()) // Set up key manager consensus parameters. - err := kmState.SetConsensusParameters(ctx, &api.ConsensusParameters{}) + err := kmState.SetConsensusParameters(ctx, &secrets.ConsensusParameters{}) require.NoError(t, err, "api.SetConsensusParameters") // Register one compute and two key manager runtimes. @@ -145,14 +147,14 @@ func TestPublishEphemeralSecret(t *testing.T) { } // Set key manager statuses. - firstKmStatus := api.Status{ + firstKmStatus := secrets.Status{ ID: firstKmID, Nodes: nodes, } err = kmState.SetStatus(ctx, &firstKmStatus) require.NoError(t, err, "keymanager.SetStatus") - secondKmStatus := api.Status{ + secondKmStatus := secrets.Status{ ID: secondKmID, Nodes: nodes, } @@ -160,11 +162,11 @@ func TestPublishEphemeralSecret(t *testing.T) { require.NoError(t, err, "keymanager.SetStatus") // Prepare signed secret. - newSignedSecret := func() *api.SignedEncryptedEphemeralSecret { - secret := api.EncryptedEphemeralSecret{ + newSignedSecret := func() *secrets.SignedEncryptedEphemeralSecret { + secret := secrets.EncryptedEphemeralSecret{ ID: firstKmID, Epoch: beacon.EpochTime(1), - Secret: api.EncryptedSecret{ + Secret: secrets.EncryptedSecret{ PubKey: *reks[0].Public(), Ciphertexts: map[x25519.PublicKey][]byte{ *reks[0].Public(): {1, 2, 3}, @@ -172,10 +174,10 @@ func TestPublishEphemeralSecret(t *testing.T) { }, }, } - sig, err2 := signature.Sign(raks[0], api.EncryptedEphemeralSecretSignatureContext, cbor.Marshal(secret)) + sig, err2 := signature.Sign(raks[0], secrets.EncryptedEphemeralSecretSignatureContext, cbor.Marshal(secret)) require.NoError(t, err2, "signature.Sign") - return &api.SignedEncryptedEphemeralSecret{ + return &secrets.SignedEncryptedEphemeralSecret{ Secret: secret, Signature: sig.Signature, } @@ -189,7 +191,7 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() sigSecret.Secret.ID = common.Namespace{} - err = app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err = ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "registry: no such runtime") }) @@ -197,16 +199,16 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() sigSecret.Secret.ID = runtimeID - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: runtime is not a key manager: 8000000000000000000000000000000000000000000000000000000000000000") }) t.Run("node not in the key manager committee", func(t *testing.T) { - err := kmState.SetStatus(ctx, &api.Status{ID: firstKmID}) + err := kmState.SetStatus(ctx, &secrets.Status{ID: firstKmID}) require.NoError(t, err, "SetStatus") sigSecret := newSignedSecret() - err = app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err = ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: ephemeral secret can be published only by the key manager committee") err = kmState.SetStatus(ctx, &firstKmStatus) @@ -217,7 +219,7 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() delete(sigSecret.Secret.Secret.Ciphertexts, *reks[0].Public()) - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: sanity check failed: secret is not encrypted with enough keys") }) @@ -225,7 +227,7 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() sigSecret.Secret.ID = secondKmID - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: sanity check failed: secret has to be encrypted with at least one key") }) @@ -233,7 +235,7 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() sigSecret.Signature = signature.RawSignature{1, 2, 3, 4, 5} - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: sanity check failed: ephemeral secret contains an invalid signature") }) @@ -241,19 +243,19 @@ func TestPublishEphemeralSecret(t *testing.T) { sigSecret := newSignedSecret() sigSecret.Secret.Epoch = 2 - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: sanity check failed: ephemeral secret contains an invalid epoch: (expected: 1, got: 2)") }) t.Run("happy path", func(t *testing.T) { sigSecret := newSignedSecret() - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.NoError(t, err, "publishEphemeralSecret") }) t.Run("ephemeral secret already published", func(t *testing.T) { sigSecret := newSignedSecret() - err := app.publishEphemeralSecret(txCtx, kmState, sigSecret) + err := ext.publishEphemeralSecret(txCtx, kmState, sigSecret) require.EqualError(t, err, "keymanager: ephemeral secret can be proposed once per epoch") }) } diff --git a/go/consensus/cometbft/apps/keymanager/state/interop/interop.go b/go/consensus/cometbft/apps/keymanager/state/interop/interop.go index 9f609257337..dfa616070c1 100644 --- a/go/consensus/cometbft/apps/keymanager/state/interop/interop.go +++ b/go/consensus/cometbft/apps/keymanager/state/interop/interop.go @@ -2,161 +2,11 @@ package interop import ( "context" - "crypto/sha512" - "encoding/hex" - "fmt" - "github.com/oasisprotocol/curve25519-voi/primitives/x25519" - - beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" - "github.com/oasisprotocol/oasis-core/go/common" - "github.com/oasisprotocol/oasis-core/go/common/cbor" - "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" - memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory" - "github.com/oasisprotocol/oasis-core/go/common/sgx" - kmState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" - kmApi "github.com/oasisprotocol/oasis-core/go/keymanager/api" + secretsInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state/interop" "github.com/oasisprotocol/oasis-core/go/storage/mkvs" ) -// InitializeTestKeyManagerState must be kept in sync with tests in runtimes/consensus/state/keymanager.rs. func InitializeTestKeyManagerState(ctx context.Context, mkvs mkvs.Tree) error { - state := kmState.NewMutableState(mkvs) - - // One runtime, two key manager runtimes. - var runtime, keymanager1, keymanager2 common.Namespace - if err := runtime.UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000000"); err != nil { - return err - } - if err := keymanager1.UnmarshalHex("c000000000000000fffffffffffffffffffffffffffffffffffffffffffffffe"); err != nil { - return err - } - if err := keymanager2.UnmarshalHex("c000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff"); err != nil { - return err - } - - // Three enclave identities. - var runtimeEnclave, keymanagerEnclave1, keymanagerEnclave2 sgx.EnclaveIdentity - if err := runtimeEnclave.MrEnclave.UnmarshalHex("18256f783c071521be2da041cd9347b5bdb5a8ef58fb34658571a6e14cf1fcb0"); err != nil { - return err - } - if err := runtimeEnclave.MrSigner.UnmarshalHex("e48049d1de0eb333523991671a6c93b97dd65bcf09273d5b6bfe8262dc968ec7"); err != nil { - return err - } - if err := keymanagerEnclave1.MrEnclave.UnmarshalHex("c9a589851b1f35627177fd70378ed778170f737611e4dfbf0b6d25bdff55b474"); err != nil { - return err - } - if err := keymanagerEnclave1.MrSigner.UnmarshalHex("7d310664780931ae103ab30a90171c201af385a72757bb4683578fdebde9adf5"); err != nil { - return err - } - if err := keymanagerEnclave2.MrEnclave.UnmarshalHex("756eaf76f5482c5345808b1eaccdd5c60f864bb2aa2d2b870df00ce435af4e23"); err != nil { - return err - } - if err := keymanagerEnclave2.MrSigner.UnmarshalHex("3597a2ff0743016f28e5d7e129304ee1c43dbdae3dba94e19cee3549038a5a32"); err != nil { - return err - } - - // Signed policy. - enclavePolicySGX := kmApi.EnclavePolicySGX{ - MayQuery: map[common.Namespace][]sgx.EnclaveIdentity{ - runtime: { - runtimeEnclave, - }, - }, - MayReplicate: []sgx.EnclaveIdentity{ - keymanagerEnclave2, - }, - } - policy := kmApi.PolicySGX{ - Serial: 1, - ID: keymanager2, - Enclaves: map[sgx.EnclaveIdentity]*kmApi.EnclavePolicySGX{ - keymanagerEnclave1: &enclavePolicySGX, - }, - MaxEphemeralSecretAge: 10, - } - sigPolicy := kmApi.SignedPolicySGX{ - Policy: policy, - Signatures: []signature.Signature{}, - } - - // Two signers. - signers := []signature.Signer{ - memorySigner.NewTestSigner("first signer"), - memorySigner.NewTestSigner("second signer"), - } - - for _, signer := range signers { - sig, err := signature.Sign(signer, kmApi.PolicySGXSignatureContext, cbor.Marshal(policy)) - if err != nil { - return fmt.Errorf("failed to sign policy: %w", err) - } - sigPolicy.Signatures = append(sigPolicy.Signatures, *sig) - } - - // Random checksum. - checksum, err := hex.DecodeString("1bff211fae98c88ba82388ae954b88a71d3bbe327e162e9fa711fe7a1b759c3e") - if err != nil { - return err - } - - // Add two statuses. - for _, status := range []*kmApi.Status{ - { - ID: keymanager1, - IsInitialized: false, - IsSecure: false, - Checksum: nil, - Nodes: nil, - Policy: nil, - RSK: nil, - }, - { - ID: keymanager2, - IsInitialized: true, - IsSecure: true, - Checksum: checksum, - Nodes: []signature.PublicKey{ - signers[0].Public(), - signers[1].Public(), - }, - Policy: &sigPolicy, - RSK: nil, - }, - } { - if err = state.SetStatus(ctx, status); err != nil { - return fmt.Errorf("setting key manager status: %w", err) - } - } - - // Add an ephemeral secret. - rek1 := x25519.PrivateKey(sha512.Sum512_256([]byte("first rek"))) - rek2 := x25519.PrivateKey(sha512.Sum512_256([]byte("second rek"))) - epoch := 1 - secret := kmApi.EncryptedEphemeralSecret{ - ID: keymanager1, - Epoch: beacon.EpochTime(epoch), - Secret: kmApi.EncryptedSecret{ - Checksum: []byte{1, 2, 3, 4, 5}, - PubKey: *rek1.Public(), - Ciphertexts: map[x25519.PublicKey][]byte{ - *rek1.Public(): {1, 2, 3}, - *rek2.Public(): {4, 5, 6}, - }, - }, - } - sig, err := signature.Sign(signers[0], kmApi.EncryptedEphemeralSecretSignatureContext, cbor.Marshal(secret)) - if err != nil { - return fmt.Errorf("failed to sign ephemeral secret: %w", err) - } - sigSecret := kmApi.SignedEncryptedEphemeralSecret{ - Secret: secret, - Signature: sig.Signature, - } - err = state.SetEphemeralSecret(ctx, &sigSecret) - if err != nil { - return fmt.Errorf("failed to set ephemeral secret: %w", err) - } - - return nil + return secretsInterop.InitializeTestKeyManagerSecretsState(ctx, mkvs) } diff --git a/go/consensus/cometbft/apps/supplementarysanity/checks.go b/go/consensus/cometbft/apps/supplementarysanity/checks.go index 8657a239178..74d7112b034 100644 --- a/go/consensus/cometbft/apps/supplementarysanity/checks.go +++ b/go/consensus/cometbft/apps/supplementarysanity/checks.go @@ -8,12 +8,12 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/quantity" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" governanceState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/governance/state" - keymanagerState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state" + secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" roothashState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state" stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" governance "github.com/oasisprotocol/oasis-core/go/governance/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registry "github.com/oasisprotocol/oasis-core/go/registry/api" roothash "github.com/oasisprotocol/oasis-core/go/roothash/api" "github.com/oasisprotocol/oasis-core/go/roothash/api/block" @@ -287,13 +287,13 @@ func checkStaking(ctx *abciAPI.Context, now beacon.EpochTime) error { //nolint: } func checkKeyManager(ctx *abciAPI.Context, _ beacon.EpochTime) error { - st := keymanagerState.NewMutableState(ctx.State()) + st := secretsState.NewMutableState(ctx.State()) statuses, err := st.Statuses(ctx) if err != nil { return fmt.Errorf("Statuses(): %w", err) } - err = keymanager.SanityCheckStatuses(statuses) + err = secrets.SanityCheckStatuses(statuses) if err != nil { return fmt.Errorf("SanityCheckStatuses: %w", err) } diff --git a/go/consensus/cometbft/keymanager/keymanager.go b/go/consensus/cometbft/keymanager/keymanager.go index 729c211dd40..fdbc9435ece 100644 --- a/go/consensus/cometbft/keymanager/keymanager.go +++ b/go/consensus/cometbft/keymanager/keymanager.go @@ -9,16 +9,12 @@ import ( cmtabcitypes "github.com/cometbft/cometbft/abci/types" cmtpubsub "github.com/cometbft/cometbft/libs/pubsub" cmttypes "github.com/cometbft/cometbft/types" - "github.com/eapache/channels" - "github.com/oasisprotocol/oasis-core/go/common/logging" - "github.com/oasisprotocol/oasis-core/go/common/pubsub" - consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" - "github.com/oasisprotocol/oasis-core/go/consensus/api/events" tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" app "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager" + "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/keymanager/api" - registry "github.com/oasisprotocol/oasis-core/go/registry/api" + secretsAPI "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) // ServiceClient is the registry service client interface. @@ -30,81 +26,22 @@ type ServiceClient interface { type serviceClient struct { tmapi.BaseServiceClient - logger *logging.Logger - - querier *app.QueryFactory - statusNotifier *pubsub.Broker - mstSecretNotifier *pubsub.Broker - ephSecretNotifier *pubsub.Broker -} - -func (sc *serviceClient) GetStatus(ctx context.Context, query *registry.NamespaceQuery) (*api.Status, error) { - q, err := sc.querier.QueryAt(ctx, query.Height) - if err != nil { - return nil, err - } - - return q.Status(ctx, query.ID) -} - -func (sc *serviceClient) GetStatuses(ctx context.Context, height int64) ([]*api.Status, error) { - q, err := sc.querier.QueryAt(ctx, height) - if err != nil { - return nil, err - } - - return q.Statuses(ctx) -} - -func (sc *serviceClient) WatchStatuses() (<-chan *api.Status, *pubsub.Subscription) { - sub := sc.statusNotifier.Subscribe() - ch := make(chan *api.Status) - sub.Unwrap(ch) - - return ch, sub + secretsClient *secrets.ServiceClient } +// Implements api.Backend. func (sc *serviceClient) StateToGenesis(ctx context.Context, height int64) (*api.Genesis, error) { - q, err := sc.querier.QueryAt(ctx, height) - if err != nil { - return nil, err - } - - return q.Genesis(ctx) -} - -func (sc *serviceClient) GetMasterSecret(ctx context.Context, query *registry.NamespaceQuery) (*api.SignedEncryptedMasterSecret, error) { - q, err := sc.querier.QueryAt(ctx, query.Height) - if err != nil { - return nil, err - } - - return q.MasterSecret(ctx, query.ID) -} - -func (sc *serviceClient) GetEphemeralSecret(ctx context.Context, query *registry.NamespaceQuery) (*api.SignedEncryptedEphemeralSecret, error) { - q, err := sc.querier.QueryAt(ctx, query.Height) + secretsGenesis, err := sc.secretsClient.StateToGenesis(ctx, height) if err != nil { return nil, err } - return q.EphemeralSecret(ctx, query.ID) -} - -func (sc *serviceClient) WatchMasterSecrets() (<-chan *api.SignedEncryptedMasterSecret, *pubsub.Subscription) { - sub := sc.mstSecretNotifier.Subscribe() - ch := make(chan *api.SignedEncryptedMasterSecret) - sub.Unwrap(ch) - - return ch, sub + return secretsGenesis, nil } -func (sc *serviceClient) WatchEphemeralSecrets() (<-chan *api.SignedEncryptedEphemeralSecret, *pubsub.Subscription) { - sub := sc.ephSecretNotifier.Subscribe() - ch := make(chan *api.SignedEncryptedEphemeralSecret) - sub.Unwrap(ch) - - return ch, sub +// Implements api.Backend. +func (sc *serviceClient) Secrets() secretsAPI.Backend { + return sc.secretsClient } // Implements api.ServiceClient. @@ -114,44 +51,7 @@ func (sc *serviceClient) ServiceDescriptor() tmapi.ServiceDescriptor { // Implements api.ServiceClient. func (sc *serviceClient) DeliverEvent(_ context.Context, _ int64, _ cmttypes.Tx, ev *cmtabcitypes.Event) error { - for _, pair := range ev.GetAttributes() { - if events.IsAttributeKind(pair.GetKey(), &api.StatusUpdateEvent{}) { - var event api.StatusUpdateEvent - if err := events.DecodeValue(pair.GetValue(), &event); err != nil { - sc.logger.Error("worker: failed to get statuses from tag", - "err", err, - ) - continue - } - - for _, status := range event.Statuses { - sc.statusNotifier.Broadcast(status) - } - } - if events.IsAttributeKind(pair.GetKey(), &api.MasterSecretPublishedEvent{}) { - var event api.MasterSecretPublishedEvent - if err := events.DecodeValue(pair.GetValue(), &event); err != nil { - sc.logger.Error("worker: failed to get master secret from tag", - "err", err, - ) - continue - } - - sc.mstSecretNotifier.Broadcast(event.Secret) - } - if events.IsAttributeKind(pair.GetKey(), &api.EphemeralSecretPublishedEvent{}) { - var event api.EphemeralSecretPublishedEvent - if err := events.DecodeValue(pair.GetValue(), &event); err != nil { - sc.logger.Error("worker: failed to get ephemeral secret from tag", - "err", err, - ) - continue - } - - sc.ephSecretNotifier.Broadcast(event.Secret) - } - } - return nil + return sc.secretsClient.DeliverEvent(ev) } // New constructs a new CometBFT backed key manager management Backend @@ -162,26 +62,15 @@ func New(ctx context.Context, backend tmapi.Backend) (ServiceClient, error) { return nil, fmt.Errorf("cometbft/keymanager: failed to register app: %w", err) } - sc := serviceClient{ - logger: logging.GetLogger("cometbft/keymanager"), - querier: a.QueryFactory().(*app.QueryFactory), - mstSecretNotifier: pubsub.NewBroker(false), - ephSecretNotifier: pubsub.NewBroker(false), + querier := a.QueryFactory().(*app.QueryFactory) + secretsClient, err := secrets.New(ctx, querier) + if err != nil { + return nil, fmt.Errorf("cometbft/keymanager: failed to create secrets client: %w", err) } - sc.statusNotifier = pubsub.NewBrokerEx(func(ch channels.Channel) { - statuses, err := sc.GetStatuses(ctx, consensus.HeightLatest) - if err != nil { - sc.logger.Error("status notifier: unable to get a list of statuses", - "err", err, - ) - return - } - wr := ch.In() - for _, v := range statuses { - wr <- v - } - }) + sc := serviceClient{ + secretsClient: secretsClient, + } return &sc, nil } diff --git a/go/consensus/cometbft/keymanager/secrets/client.go b/go/consensus/cometbft/keymanager/secrets/client.go new file mode 100644 index 00000000000..b2226a0983c --- /dev/null +++ b/go/consensus/cometbft/keymanager/secrets/client.go @@ -0,0 +1,164 @@ +// Package keymanager provides the CometBFT backed key manager management +// implementation. +package secrets + +import ( + "context" + + cmtabcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/eapache/channels" + + "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-core/go/common/pubsub" + consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" + "github.com/oasisprotocol/oasis-core/go/consensus/api/events" + app "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" +) + +type ServiceClient struct { + logger *logging.Logger + + querier *app.QueryFactory + statusNotifier *pubsub.Broker + mstSecretNotifier *pubsub.Broker + ephSecretNotifier *pubsub.Broker +} + +func (sc *ServiceClient) GetStatus(ctx context.Context, query *registry.NamespaceQuery) (*secrets.Status, error) { + q, err := sc.querier.QueryAt(ctx, query.Height) + if err != nil { + return nil, err + } + + return q.Secrets().Status(ctx, query.ID) +} + +func (sc *ServiceClient) GetStatuses(ctx context.Context, height int64) ([]*secrets.Status, error) { + q, err := sc.querier.QueryAt(ctx, height) + if err != nil { + return nil, err + } + + return q.Secrets().Statuses(ctx) +} + +func (sc *ServiceClient) WatchStatuses() (<-chan *secrets.Status, *pubsub.Subscription) { + sub := sc.statusNotifier.Subscribe() + ch := make(chan *secrets.Status) + sub.Unwrap(ch) + + return ch, sub +} + +func (sc *ServiceClient) StateToGenesis(ctx context.Context, height int64) (*secrets.Genesis, error) { + q, err := sc.querier.QueryAt(ctx, height) + if err != nil { + return nil, err + } + + return q.Secrets().Genesis(ctx) +} + +func (sc *ServiceClient) GetMasterSecret(ctx context.Context, query *registry.NamespaceQuery) (*secrets.SignedEncryptedMasterSecret, error) { + q, err := sc.querier.QueryAt(ctx, query.Height) + if err != nil { + return nil, err + } + + return q.Secrets().MasterSecret(ctx, query.ID) +} + +func (sc *ServiceClient) GetEphemeralSecret(ctx context.Context, query *registry.NamespaceQuery) (*secrets.SignedEncryptedEphemeralSecret, error) { + q, err := sc.querier.QueryAt(ctx, query.Height) + if err != nil { + return nil, err + } + + return q.Secrets().EphemeralSecret(ctx, query.ID) +} + +func (sc *ServiceClient) WatchMasterSecrets() (<-chan *secrets.SignedEncryptedMasterSecret, *pubsub.Subscription) { + sub := sc.mstSecretNotifier.Subscribe() + ch := make(chan *secrets.SignedEncryptedMasterSecret) + sub.Unwrap(ch) + + return ch, sub +} + +func (sc *ServiceClient) WatchEphemeralSecrets() (<-chan *secrets.SignedEncryptedEphemeralSecret, *pubsub.Subscription) { + sub := sc.ephSecretNotifier.Subscribe() + ch := make(chan *secrets.SignedEncryptedEphemeralSecret) + sub.Unwrap(ch) + + return ch, sub +} + +func (sc *ServiceClient) DeliverEvent(ev *cmtabcitypes.Event) error { + for _, pair := range ev.GetAttributes() { + if events.IsAttributeKind(pair.GetKey(), &secrets.StatusUpdateEvent{}) { + var event secrets.StatusUpdateEvent + if err := events.DecodeValue(pair.GetValue(), &event); err != nil { + sc.logger.Error("worker: failed to get statuses from tag", + "err", err, + ) + continue + } + + for _, status := range event.Statuses { + sc.statusNotifier.Broadcast(status) + } + } + if events.IsAttributeKind(pair.GetKey(), &secrets.MasterSecretPublishedEvent{}) { + var event secrets.MasterSecretPublishedEvent + if err := events.DecodeValue(pair.GetValue(), &event); err != nil { + sc.logger.Error("worker: failed to get master secret from tag", + "err", err, + ) + continue + } + + sc.mstSecretNotifier.Broadcast(event.Secret) + } + if events.IsAttributeKind(pair.GetKey(), &secrets.EphemeralSecretPublishedEvent{}) { + var event secrets.EphemeralSecretPublishedEvent + if err := events.DecodeValue(pair.GetValue(), &event); err != nil { + sc.logger.Error("worker: failed to get ephemeral secret from tag", + "err", err, + ) + continue + } + + sc.ephSecretNotifier.Broadcast(event.Secret) + } + } + return nil +} + +// New constructs a new CometBFT backed key manager secrets management Backend +// instance. +func New(ctx context.Context, querier *app.QueryFactory) (*ServiceClient, error) { + sc := ServiceClient{ + logger: logging.GetLogger("cometbft/keymanager/secrets"), + querier: querier, + mstSecretNotifier: pubsub.NewBroker(false), + ephSecretNotifier: pubsub.NewBroker(false), + } + sc.statusNotifier = pubsub.NewBrokerEx(func(ch channels.Channel) { + statuses, err := sc.GetStatuses(ctx, consensus.HeightLatest) + if err != nil { + sc.logger.Error("status notifier: unable to get a list of statuses", + "err", err, + ) + return + } + + wr := ch.In() + for _, v := range statuses { + wr <- v + } + }) + + return &sc, nil +} diff --git a/go/genesis/genesis_test.go b/go/genesis/genesis_test.go index 797c682b73d..7efff02f346 100644 --- a/go/genesis/genesis_test.go +++ b/go/genesis/genesis_test.go @@ -26,6 +26,7 @@ import ( genesisTestHelpers "github.com/oasisprotocol/oasis-core/go/genesis/tests" governance "github.com/oasisprotocol/oasis-core/go/governance/api" keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" cmdFlags "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" registry "github.com/oasisprotocol/oasis-core/go/registry/api" roothash "github.com/oasisprotocol/oasis-core/go/roothash/api" @@ -314,7 +315,7 @@ func TestGenesisSanityCheck(t *testing.T) { // Test keymanager genesis checks. d = testDoc() d.KeyManager = keymanager.Genesis{ - Statuses: []*keymanager.Status{ + Statuses: []*secrets.Status{ { ID: testRuntimeID, }, @@ -324,7 +325,7 @@ func TestGenesisSanityCheck(t *testing.T) { d = testDoc() d.KeyManager = keymanager.Genesis{ - Statuses: []*keymanager.Status{ + Statuses: []*secrets.Status{ { ID: validNS, Nodes: []signature.PublicKey{invalidPK}, diff --git a/go/keymanager/api/api.go b/go/keymanager/api/api.go index 4b65e0fee16..1ceaf9ce6fe 100644 --- a/go/keymanager/api/api.go +++ b/go/keymanager/api/api.go @@ -4,59 +4,20 @@ package api import ( "context" "crypto/sha512" - "fmt" - "time" "github.com/oasisprotocol/curve25519-voi/primitives/x25519" - beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" - "github.com/oasisprotocol/oasis-core/go/common" - "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory" - "github.com/oasisprotocol/oasis-core/go/common/errors" - "github.com/oasisprotocol/oasis-core/go/common/logging" - "github.com/oasisprotocol/oasis-core/go/common/node" - "github.com/oasisprotocol/oasis-core/go/common/pubsub" - "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" - registry "github.com/oasisprotocol/oasis-core/go/registry/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) const ( // ModuleName is a unique module name for the keymanager module. ModuleName = "keymanager" - - // ChecksumSize is the length of checksum in bytes. - ChecksumSize = 32 - - // KeyPairIDSize is the size of a key pair ID in bytes. - KeyPairIDSize = 32 ) var ( - // ErrInvalidArgument is the error returned on malformed arguments. - ErrInvalidArgument = errors.New(ModuleName, 1, "keymanager: invalid argument") - - // ErrNoSuchStatus is the error returned when a key manager status does not - // exist. - ErrNoSuchStatus = errors.New(ModuleName, 2, "keymanager: no such status") - - // ErrNoSuchMasterSecret is the error returned when a key manager master secret does not exist. - ErrNoSuchMasterSecret = errors.New(ModuleName, 3, "keymanager: no such master secret") - - // ErrNoSuchEphemeralSecret is the error returned when a key manager ephemeral secret - // does not exist. - ErrNoSuchEphemeralSecret = errors.New(ModuleName, 4, "keymanager: no such ephemeral secret") - - // MethodUpdatePolicy is the method name for policy updates. - MethodUpdatePolicy = transaction.NewMethodName(ModuleName, "UpdatePolicy", SignedPolicySGX{}) - - // MethodPublishMasterSecret is the method name for publishing master secret. - MethodPublishMasterSecret = transaction.NewMethodName(ModuleName, "PublishMasterSecret", SignedEncryptedMasterSecret{}) - - // MethodPublishEphemeralSecret is the method name for publishing ephemeral secret. - MethodPublishEphemeralSecret = transaction.NewMethodName(ModuleName, "PublishEphemeralSecret", SignedEncryptedEphemeralSecret{}) - // InsecureRAK is the insecure hardcoded key manager public key, used // in insecure builds when a RAK is unavailable. InsecureRAK signature.PublicKey @@ -69,392 +30,21 @@ var ( // in insecure builds when a RAK is unavailable. TestSigners []signature.Signer - // Methods is the list of all methods supported by the key manager backend. - Methods = []transaction.MethodName{ - MethodUpdatePolicy, - MethodPublishMasterSecret, - MethodPublishEphemeralSecret, - } - // RPCMethodConnect is the name of the method used to establish a Noise session. RPCMethodConnect = "" - - // RPCMethodInit is the name of the `init` method. - RPCMethodInit = "init" - - // RPCMethodGetOrCreateKeys is the name of the `get_or_create_keys` method. - RPCMethodGetOrCreateKeys = "get_or_create_keys" - - // RPCMethodGetPublicKey is the name of the `get_public_key` method. - RPCMethodGetPublicKey = "get_public_key" - - // RPCMethodGetOrCreateEphemeralKeys is the name of the `get_or_create_ephemeral_keys` method. - RPCMethodGetOrCreateEphemeralKeys = "get_or_create_ephemeral_keys" - - // RPCMethodGetPublicEphemeralKey is the name of the `get_public_ephemeral_key` method. - RPCMethodGetPublicEphemeralKey = "get_public_ephemeral_key" // #nosec G101 - - // RPCMethodReplicateMasterSecret is the name of the `replicate_master_secret` method. - RPCMethodReplicateMasterSecret = "replicate_master_secret" - - // RPCMethodReplicateEphemeralSecret is the name of the `replicate_ephemeral_secret` method. - RPCMethodReplicateEphemeralSecret = "replicate_ephemeral_secret" - - // RPCMethodGenerateMasterSecret is the name of the `generate_master_secret` RPC method. - RPCMethodGenerateMasterSecret = "generate_master_secret" - - // RPCMethodGenerateEphemeralSecret is the name of the `generate_ephemeral_secret` RPC method. - RPCMethodGenerateEphemeralSecret = "generate_ephemeral_secret" - - // RPCMethodLoadMasterSecret is the name of the `load_master_secret` RPC method. - RPCMethodLoadMasterSecret = "load_master_secret" - - // RPCMethodLoadEphemeralSecret is the name of the `load_ephemeral_secret` RPC method. - RPCMethodLoadEphemeralSecret = "load_ephemeral_secret" - - // initResponseSignatureContext is the context used to sign key manager init responses. - initResponseSignatureContext = signature.NewContext("oasis-core/keymanager: init response") ) -const ( - // GasOpUpdatePolicy is the gas operation identifier for policy updates - // costs. - GasOpUpdatePolicy transaction.Op = "update_policy" - // GasOpPublishMasterSecret is the gas operation identifier for publishing - // key manager master secret. - GasOpPublishMasterSecret transaction.Op = "publish_master_secret" - // GasOpPublishEphemeralSecret is the gas operation identifier for publishing - // key manager ephemeral secret. - GasOpPublishEphemeralSecret transaction.Op = "publish_ephemeral_secret" -) - -// XXX: Define reasonable default gas costs. - -// DefaultGasCosts are the "default" gas costs for operations. -var DefaultGasCosts = transaction.Costs{ - GasOpUpdatePolicy: 1000, - GasOpPublishMasterSecret: 1000, - GasOpPublishEphemeralSecret: 1000, -} - -// KeyPairID is a 256-bit key pair identifier. -type KeyPairID [KeyPairIDSize]byte - -// Status is the current key manager status. -type Status struct { - // ID is the runtime ID of the key manager. - ID common.Namespace `json:"id"` - - // IsInitialized is true iff the key manager is done initializing. - IsInitialized bool `json:"is_initialized"` - - // IsSecure is true iff the key manager is secure. - IsSecure bool `json:"is_secure"` - - // Generation is the generation of the latest master secret. - Generation uint64 `json:"generation,omitempty"` - - // RotationEpoch is the epoch of the last master secret rotation. - RotationEpoch beacon.EpochTime `json:"rotation_epoch,omitempty"` - - // Checksum is the key manager master secret verification checksum. - Checksum []byte `json:"checksum"` - - // Nodes is the list of currently active key manager node IDs. - Nodes []signature.PublicKey `json:"nodes"` - - // Policy is the key manager policy. - Policy *SignedPolicySGX `json:"policy"` - - // RSK is the runtime signing key of the key manager. - RSK *signature.PublicKey `json:"rsk,omitempty"` -} - -// NextGeneration returns the generation of the next master secret. -func (s *Status) NextGeneration() uint64 { - if len(s.Checksum) == 0 { - return 0 - } - return s.Generation + 1 -} - -// VerifyRotationEpoch verifies if rotation can be performed in the given epoch. -func (s *Status) VerifyRotationEpoch(epoch beacon.EpochTime) error { - if nextGen := s.NextGeneration(); nextGen == 0 { - return nil - } - - // By default, rotation is disabled unless specified in the policy. - var rotationInterval beacon.EpochTime - if s.Policy != nil { - rotationInterval = s.Policy.Policy.MasterSecretRotationInterval - } - - // Reject if rotation is disabled. - if rotationInterval == 0 { - return fmt.Errorf("master secret rotation disabled") - } - - // Reject if the rotation period has not expired. - rotationEpoch := s.RotationEpoch + rotationInterval - if epoch < rotationEpoch { - return fmt.Errorf("master secret rotation interval has not yet expired") - } - - return nil -} - // Backend is a key manager management implementation. type Backend interface { - // GetStatus returns a key manager status by key manager ID. - GetStatus(context.Context, *registry.NamespaceQuery) (*Status, error) - - // GetStatuses returns all currently tracked key manager statuses. - GetStatuses(context.Context, int64) ([]*Status, error) - - // WatchStatuses returns a channel that produces a stream of messages - // containing the key manager statuses as it changes over time. - // - // Upon subscription the current status is sent immediately. - WatchStatuses() (<-chan *Status, *pubsub.Subscription) - // StateToGenesis returns the genesis state at specified block height. - StateToGenesis(context.Context, int64) (*Genesis, error) - - // GetMasterSecret returns the key manager master secret. - GetMasterSecret(context.Context, *registry.NamespaceQuery) (*SignedEncryptedMasterSecret, error) - - // WatchMasterSecrets returns a channel that produces a stream of master secrets. - WatchMasterSecrets() (<-chan *SignedEncryptedMasterSecret, *pubsub.Subscription) - - // GetEphemeralSecret returns the key manager ephemeral secret. - GetEphemeralSecret(context.Context, *registry.NamespaceQuery) (*SignedEncryptedEphemeralSecret, error) - - // WatchEphemeralSecrets returns a channel that produces a stream of ephemeral secrets. - WatchEphemeralSecrets() (<-chan *SignedEncryptedEphemeralSecret, *pubsub.Subscription) -} - -// NewUpdatePolicyTx creates a new policy update transaction. -func NewUpdatePolicyTx(nonce uint64, fee *transaction.Fee, sigPol *SignedPolicySGX) *transaction.Transaction { - return transaction.NewTransaction(nonce, fee, MethodUpdatePolicy, sigPol) -} - -// NewPublishMasterSecretTx creates a new publish master secret transaction. -func NewPublishMasterSecretTx(nonce uint64, fee *transaction.Fee, sigSec *SignedEncryptedMasterSecret) *transaction.Transaction { - return transaction.NewTransaction(nonce, fee, MethodPublishMasterSecret, sigSec) -} - -// NewPublishEphemeralSecretTx creates a new publish ephemeral secret transaction. -func NewPublishEphemeralSecretTx(nonce uint64, fee *transaction.Fee, sigSec *SignedEncryptedEphemeralSecret) *transaction.Transaction { - return transaction.NewTransaction(nonce, fee, MethodPublishEphemeralSecret, sigSec) -} - -// InitRequest is the initialization RPC request, sent to the key manager -// enclave. -type InitRequest struct { - Status Status `json:"status,omitempty"` -} - -// InitResponse is the initialization RPC response, returned as part of a -// SignedInitResponse from the key manager enclave. -type InitResponse struct { - IsSecure bool `json:"is_secure"` - Checksum []byte `json:"checksum"` - NextChecksum []byte `json:"next_checksum,omitempty"` - PolicyChecksum []byte `json:"policy_checksum"` - RSK *signature.PublicKey `json:"rsk,omitempty"` - NextRSK *signature.PublicKey `json:"next_rsk,omitempty"` -} - -// SignedInitResponse is the signed initialization RPC response, returned -// from the key manager enclave. -type SignedInitResponse struct { - InitResponse InitResponse `json:"init_response"` - Signature []byte `json:"signature"` -} - -// Verify verifies the signature of the init response using the given key. -func (r *SignedInitResponse) Verify(pk signature.PublicKey) error { - raw := cbor.Marshal(r.InitResponse) - if !pk.Verify(initResponseSignatureContext, raw, r.Signature) { - return fmt.Errorf("keymanager: invalid initialization response signature") - } - return nil -} - -// SignInitResponse signs the given init response. -func SignInitResponse(signer signature.Signer, response *InitResponse) (*SignedInitResponse, error) { - sig, err := signer.ContextSign(initResponseSignatureContext, cbor.Marshal(response)) - if err != nil { - return nil, err - } - return &SignedInitResponse{ - InitResponse: *response, - Signature: sig, - }, nil -} - -// LongTermKeyRequest is the long-term key RPC request, sent to the key manager -// enclave. -type LongTermKeyRequest struct { - Height *uint64 `json:"height"` - ID common.Namespace `json:"runtime_id"` - KeyPairID KeyPairID `json:"key_pair_id"` - Generation uint64 `json:"generation"` -} - -// EphemeralKeyRequest is the ephemeral key RPC request, sent to the key manager -// enclave. -type EphemeralKeyRequest struct { - Height *uint64 `json:"height"` - ID common.Namespace `json:"runtime_id"` - KeyPairID KeyPairID `json:"key_pair_id"` - Epoch beacon.EpochTime `json:"epoch"` -} - -// SignedPublicKey is the RPC response, returned as part of -// an EphemeralKeyRequest from the key manager enclave. -type SignedPublicKey struct { - Key x25519.PublicKey `json:"key"` - Checksum []byte `json:"checksum"` - Signature signature.RawSignature `json:"signature"` - Expiration *beacon.EpochTime `json:"expiration,omitempty"` -} - -// GenerateMasterSecretRequest is the generate master secret RPC request, -// sent to the key manager enclave. -type GenerateMasterSecretRequest struct { - Generation uint64 `json:"generation"` - Epoch beacon.EpochTime `json:"epoch"` -} - -// GenerateMasterSecretResponse is the RPC response, returned as part of -// a GenerateMasterSecretRequest from the key manager enclave. -type GenerateMasterSecretResponse struct { - SignedSecret SignedEncryptedMasterSecret `json:"signed_secret"` -} - -// GenerateEphemeralSecretRequest is the generate ephemeral secret RPC request, -// sent to the key manager enclave. -type GenerateEphemeralSecretRequest struct { - Epoch beacon.EpochTime `json:"epoch"` -} - -// GenerateEphemeralSecretResponse is the RPC response, returned as part of -// a GenerateEphemeralSecretRequest from the key manager enclave. -type GenerateEphemeralSecretResponse struct { - SignedSecret SignedEncryptedEphemeralSecret `json:"signed_secret"` -} + StateToGenesis(ctx context.Context, height int64) (*Genesis, error) -// LoadMasterSecretRequest is the load master secret RPC request, -// sent to the key manager enclave. -type LoadMasterSecretRequest struct { - SignedSecret SignedEncryptedMasterSecret `json:"signed_secret"` -} - -// LoadEphemeralSecretRequest is the load ephemeral secret RPC request, -// sent to the key manager enclave. -type LoadEphemeralSecretRequest struct { - SignedSecret SignedEncryptedEphemeralSecret `json:"signed_secret"` -} - -// VerifyExtraInfo verifies and parses the per-node + per-runtime ExtraInfo -// blob for a key manager. -func VerifyExtraInfo( - logger *logging.Logger, - nodeID signature.PublicKey, - rt *registry.Runtime, - nodeRt *node.Runtime, - ts time.Time, - height uint64, - params *registry.ConsensusParameters, -) (*InitResponse, error) { - var ( - hw node.TEEHardware - rak signature.PublicKey - ) - if nodeRt.Capabilities.TEE == nil || nodeRt.Capabilities.TEE.Hardware == node.TEEHardwareInvalid { - hw = node.TEEHardwareInvalid - rak = InsecureRAK - } else { - hw = nodeRt.Capabilities.TEE.Hardware - rak = nodeRt.Capabilities.TEE.RAK - } - if hw != rt.TEEHardware { - return nil, fmt.Errorf("keymanager: TEEHardware mismatch") - } else if err := registry.VerifyNodeRuntimeEnclaveIDs(logger, nodeID, nodeRt, rt, params.TEEFeatures, ts, height); err != nil { - return nil, err - } - if nodeRt.ExtraInfo == nil { - return nil, fmt.Errorf("keymanager: missing ExtraInfo") - } - - var untrustedSignedInitResponse SignedInitResponse - if err := cbor.Unmarshal(nodeRt.ExtraInfo, &untrustedSignedInitResponse); err != nil { - return nil, err - } - if err := untrustedSignedInitResponse.Verify(rak); err != nil { - return nil, err - } - return &untrustedSignedInitResponse.InitResponse, nil + // Secrets returns the key manager secrets management implementation. + Secrets() secrets.Backend } // Genesis is the key manager management genesis state. -type Genesis struct { - // Parameters are the key manager consensus parameters. - Parameters ConsensusParameters `json:"params"` - - Statuses []*Status `json:"statuses,omitempty"` -} - -// ConsensusParameters are the key manager consensus parameters. -type ConsensusParameters struct { - GasCosts transaction.Costs `json:"gas_costs,omitempty"` -} - -// ConsensusParameterChanges are allowed key manager consensus parameter changes. -type ConsensusParameterChanges struct { - // GasCosts are the new gas costs. - GasCosts transaction.Costs `json:"gas_costs,omitempty"` -} - -// Apply applies changes to the given consensus parameters. -func (c *ConsensusParameterChanges) Apply(params *ConsensusParameters) error { - if c.GasCosts != nil { - params.GasCosts = c.GasCosts - } - return nil -} - -// StatusUpdateEvent is the keymanager status update event. -type StatusUpdateEvent struct { - Statuses []*Status -} - -// EventKind returns a string representation of this event's kind. -func (ev *StatusUpdateEvent) EventKind() string { - return "status" -} - -// MasterSecretPublishedEvent is the key manager master secret published event. -type MasterSecretPublishedEvent struct { - Secret *SignedEncryptedMasterSecret -} - -// EventKind returns a string representation of this event's kind. -func (ev *MasterSecretPublishedEvent) EventKind() string { - return "master_secret" -} - -// EphemeralSecretPublishedEvent is the key manager ephemeral secret published event. -type EphemeralSecretPublishedEvent struct { - Secret *SignedEncryptedEphemeralSecret -} - -// EventKind returns a string representation of this event's kind. -func (ev *EphemeralSecretPublishedEvent) EventKind() string { - return "ephemeral_secret" -} +type Genesis = secrets.Genesis func init() { // Old `INSECURE_SIGNING_KEY_PKCS8`. diff --git a/go/keymanager/api/grpc.go b/go/keymanager/api/grpc.go index 666847e28ad..094bb7e48d3 100644 --- a/go/keymanager/api/grpc.go +++ b/go/keymanager/api/grpc.go @@ -1,392 +1,28 @@ package api import ( - "context" - "google.golang.org/grpc" - cmnGrpc "github.com/oasisprotocol/oasis-core/go/common/grpc" - "github.com/oasisprotocol/oasis-core/go/common/pubsub" - registry "github.com/oasisprotocol/oasis-core/go/registry/api" -) - -var ( - // serviceName is the gRPC service name. - serviceName = cmnGrpc.NewServiceName("KeyManager") - - // methodGetStatus is the GetStatus method. - methodGetStatus = serviceName.NewMethod("GetStatus", registry.NamespaceQuery{}) - // methodGetStatuses is the GetStatuses method. - methodGetStatuses = serviceName.NewMethod("GetStatuses", int64(0)) - // methodGetMasterSecret is the GetMasterSecret method. - methodGetMasterSecret = serviceName.NewMethod("GetMasterSecret", registry.NamespaceQuery{}) - // methodGetEphemeralSecret is the GetEphemeralSecret method. - methodGetEphemeralSecret = serviceName.NewMethod("GetEphemeralSecret", registry.NamespaceQuery{}) - - // methodWatchStatuses is the WatchStatuses method. - methodWatchStatuses = serviceName.NewMethod("WatchStatuses", nil) - // methodWatchMasterSecrets is the WatchMasterSecrets method. - methodWatchMasterSecrets = serviceName.NewMethod("WatchMasterSecrets", nil) - // methodWatchEphemeralSecrets is the WatchEphemeralSecrets method. - methodWatchEphemeralSecrets = serviceName.NewMethod("WatchEphemeralSecrets", nil) - - // serviceDesc is the gRPC service descriptor. - serviceDesc = grpc.ServiceDesc{ - ServiceName: string(serviceName), - HandlerType: (*Backend)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: methodGetStatus.ShortName(), - Handler: handlerGetStatus, - }, - { - MethodName: methodGetStatuses.ShortName(), - Handler: handlerGetStatuses, - }, - { - MethodName: methodGetMasterSecret.ShortName(), - Handler: handlerGetMasterSecret, - }, - { - MethodName: methodGetEphemeralSecret.ShortName(), - Handler: handlerGetEphemeralSecret, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: methodWatchStatuses.ShortName(), - Handler: handlerWatchStatuses, - ServerStreams: true, - }, - { - StreamName: methodWatchMasterSecrets.ShortName(), - Handler: handlerWatchMasterSecrets, - ServerStreams: true, - }, - { - StreamName: methodWatchEphemeralSecrets.ShortName(), - Handler: handlerWatchEphemeralSecrets, - ServerStreams: true, - }, - }, - } + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" ) -func handlerGetStatus( - srv interface{}, - ctx context.Context, - dec func(interface{}) error, - interceptor grpc.UnaryServerInterceptor, -) (interface{}, error) { - var query registry.NamespaceQuery - if err := dec(&query); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(Backend).GetStatus(ctx, &query) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: methodGetStatus.FullName(), - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(Backend).GetStatus(ctx, req.(*registry.NamespaceQuery)) - } - return interceptor(ctx, &query, info, handler) -} - -func handlerGetStatuses( - srv interface{}, - ctx context.Context, - dec func(interface{}) error, - interceptor grpc.UnaryServerInterceptor, -) (interface{}, error) { - var height int64 - if err := dec(&height); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(Backend).GetStatuses(ctx, height) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: methodGetStatuses.FullName(), - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(Backend).GetStatuses(ctx, req.(int64)) - } - return interceptor(ctx, height, info, handler) -} - -func handlerGetMasterSecret( - srv interface{}, - ctx context.Context, - dec func(interface{}) error, - interceptor grpc.UnaryServerInterceptor, -) (interface{}, error) { - var query registry.NamespaceQuery - if err := dec(&query); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(Backend).GetMasterSecret(ctx, &query) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: methodGetMasterSecret.FullName(), - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(Backend).GetMasterSecret(ctx, req.(*registry.NamespaceQuery)) - } - return interceptor(ctx, &query, info, handler) -} - -func handlerGetEphemeralSecret( - srv interface{}, - ctx context.Context, - dec func(interface{}) error, - interceptor grpc.UnaryServerInterceptor, -) (interface{}, error) { - var query registry.NamespaceQuery - if err := dec(&query); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(Backend).GetEphemeralSecret(ctx, &query) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: methodGetEphemeralSecret.FullName(), - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(Backend).GetEphemeralSecret(ctx, req.(*registry.NamespaceQuery)) - } - return interceptor(ctx, &query, info, handler) -} - -func handlerWatchStatuses(srv interface{}, stream grpc.ServerStream) error { - if err := stream.RecvMsg(nil); err != nil { - return err - } - - ctx := stream.Context() - ch, sub := srv.(Backend).WatchStatuses() - defer sub.Close() - - for { - select { - case stat, ok := <-ch: - if !ok { - return nil - } - - if err := stream.SendMsg(stat); err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func handlerWatchMasterSecrets(srv interface{}, stream grpc.ServerStream) error { - if err := stream.RecvMsg(nil); err != nil { - return err - } - - ctx := stream.Context() - ch, sub := srv.(Backend).WatchMasterSecrets() - defer sub.Close() - - for { - select { - case sec, ok := <-ch: - if !ok { - return nil - } - - if err := stream.SendMsg(sec); err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func handlerWatchEphemeralSecrets(srv interface{}, stream grpc.ServerStream) error { - if err := stream.RecvMsg(nil); err != nil { - return err - } - - ctx := stream.Context() - ch, sub := srv.(Backend).WatchEphemeralSecrets() - defer sub.Close() - - for { - select { - case sec, ok := <-ch: - if !ok { - return nil - } - - if err := stream.SendMsg(sec); err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - // RegisterService registers a new keymanager backend service with the given gRPC server. func RegisterService(server *grpc.Server, service Backend) { - server.RegisterService(&serviceDesc, service) + secrets.RegisterService(server, service.Secrets()) } // KeymanagerClient is a gRPC keymanager client. type KeymanagerClient struct { - conn *grpc.ClientConn -} - -func (c *KeymanagerClient) GetStatus(ctx context.Context, query *registry.NamespaceQuery) (*Status, error) { - var resp Status - if err := c.conn.Invoke(ctx, methodGetStatus.FullName(), query, &resp); err != nil { - return nil, err - } - return &resp, nil -} - -func (c *KeymanagerClient) GetStatuses(ctx context.Context, height int64) ([]*Status, error) { - var resp []*Status - if err := c.conn.Invoke(ctx, methodGetStatuses.FullName(), height, &resp); err != nil { - return nil, err - } - return resp, nil -} - -func (c *KeymanagerClient) GetMasterSecret(ctx context.Context, query *registry.NamespaceQuery) (*SignedEncryptedMasterSecret, error) { - var resp *SignedEncryptedMasterSecret - if err := c.conn.Invoke(ctx, methodGetMasterSecret.FullName(), query, &resp); err != nil { - return nil, err - } - return resp, nil -} - -func (c *KeymanagerClient) GetEphemeralSecret(ctx context.Context, query *registry.NamespaceQuery) (*SignedEncryptedEphemeralSecret, error) { - var resp *SignedEncryptedEphemeralSecret - if err := c.conn.Invoke(ctx, methodGetEphemeralSecret.FullName(), query, &resp); err != nil { - return nil, err - } - return resp, nil -} - -func (c *KeymanagerClient) WatchStatuses(ctx context.Context) (<-chan *Status, pubsub.ClosableSubscription, error) { - ctx, sub := pubsub.NewContextSubscription(ctx) - - stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchStatuses.FullName()) - if err != nil { - return nil, nil, err - } - if err = stream.SendMsg(nil); err != nil { - return nil, nil, err - } - if err = stream.CloseSend(); err != nil { - return nil, nil, err - } - - ch := make(chan *Status) - go func() { - defer close(ch) - - for { - var stat Status - if serr := stream.RecvMsg(&stat); serr != nil { - return - } - - select { - case ch <- &stat: - case <-ctx.Done(): - return - } - } - }() - - return ch, sub, nil + secretsClient *secrets.Client } -func (c *KeymanagerClient) WatchMasterSecrets(ctx context.Context) (<-chan *SignedEncryptedMasterSecret, pubsub.ClosableSubscription, error) { - ctx, sub := pubsub.NewContextSubscription(ctx) - - stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchMasterSecrets.FullName()) - if err != nil { - return nil, nil, err - } - if err = stream.SendMsg(nil); err != nil { - return nil, nil, err - } - if err = stream.CloseSend(); err != nil { - return nil, nil, err - } - - ch := make(chan *SignedEncryptedMasterSecret) - go func() { - defer close(ch) - - for { - var sec SignedEncryptedMasterSecret - if serr := stream.RecvMsg(&sec); serr != nil { - return - } - - select { - case ch <- &sec: - case <-ctx.Done(): - return - } - } - }() - - return ch, sub, nil -} - -func (c *KeymanagerClient) WatchEphemeralSecrets(ctx context.Context) (<-chan *SignedEncryptedEphemeralSecret, pubsub.ClosableSubscription, error) { - ctx, sub := pubsub.NewContextSubscription(ctx) - - stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchEphemeralSecrets.FullName()) - if err != nil { - return nil, nil, err - } - if err = stream.SendMsg(nil); err != nil { - return nil, nil, err - } - if err = stream.CloseSend(); err != nil { - return nil, nil, err - } - - ch := make(chan *SignedEncryptedEphemeralSecret) - go func() { - defer close(ch) - - for { - var sec SignedEncryptedEphemeralSecret - if serr := stream.RecvMsg(&sec); serr != nil { - return - } - - select { - case ch <- &sec: - case <-ctx.Done(): - return - } - } - }() - - return ch, sub, nil +func (c *KeymanagerClient) Secrets() *secrets.Client { + return c.secretsClient } // NewKeymanagerClient creates a new gRPC keymanager client service. func NewKeymanagerClient(c *grpc.ClientConn) *KeymanagerClient { - return &KeymanagerClient{c} + return &KeymanagerClient{ + secretsClient: secrets.NewClient(c), + } } diff --git a/go/keymanager/secrets/api.go b/go/keymanager/secrets/api.go new file mode 100644 index 00000000000..e95b498fa29 --- /dev/null +++ b/go/keymanager/secrets/api.go @@ -0,0 +1,396 @@ +// Package api implements the key manager management API and common data types. +package secrets + +import ( + "context" + "fmt" + + "github.com/oasisprotocol/curve25519-voi/primitives/x25519" + + beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" + "github.com/oasisprotocol/oasis-core/go/common/errors" + "github.com/oasisprotocol/oasis-core/go/common/pubsub" + "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" +) + +const ( + // moduleName is a unique module name for the keymanager module. + moduleName = "keymanager" + + // ChecksumSize is the length of checksum in bytes. + ChecksumSize = 32 + + // KeyPairIDSize is the size of a key pair ID in bytes. + KeyPairIDSize = 32 +) + +var ( + // ErrInvalidArgument is the error returned on malformed arguments. + ErrInvalidArgument = errors.New(moduleName, 1, "keymanager: invalid argument") + + // ErrNoSuchStatus is the error returned when a key manager status does not + // exist. + ErrNoSuchStatus = errors.New(moduleName, 2, "keymanager: no such status") + + // ErrNoSuchMasterSecret is the error returned when a key manager master secret does not exist. + ErrNoSuchMasterSecret = errors.New(moduleName, 3, "keymanager: no such master secret") + + // ErrNoSuchEphemeralSecret is the error returned when a key manager ephemeral secret + // does not exist. + ErrNoSuchEphemeralSecret = errors.New(moduleName, 4, "keymanager: no such ephemeral secret") + + // MethodUpdatePolicy is the method name for policy updates. + MethodUpdatePolicy = transaction.NewMethodName(moduleName, "UpdatePolicy", SignedPolicySGX{}) + + // MethodPublishMasterSecret is the method name for publishing master secret. + MethodPublishMasterSecret = transaction.NewMethodName(moduleName, "PublishMasterSecret", SignedEncryptedMasterSecret{}) + + // MethodPublishEphemeralSecret is the method name for publishing ephemeral secret. + MethodPublishEphemeralSecret = transaction.NewMethodName(moduleName, "PublishEphemeralSecret", SignedEncryptedEphemeralSecret{}) + + // Methods is the list of all methods supported by the key manager backend. + Methods = []transaction.MethodName{ + MethodUpdatePolicy, + MethodPublishMasterSecret, + MethodPublishEphemeralSecret, + } + + // RPCMethodInit is the name of the `init` method. + RPCMethodInit = "init" + + // RPCMethodGetOrCreateKeys is the name of the `get_or_create_keys` method. + RPCMethodGetOrCreateKeys = "get_or_create_keys" + + // RPCMethodGetPublicKey is the name of the `get_public_key` method. + RPCMethodGetPublicKey = "get_public_key" + + // RPCMethodGetOrCreateEphemeralKeys is the name of the `get_or_create_ephemeral_keys` method. + RPCMethodGetOrCreateEphemeralKeys = "get_or_create_ephemeral_keys" + + // RPCMethodGetPublicEphemeralKey is the name of the `get_public_ephemeral_key` method. + RPCMethodGetPublicEphemeralKey = "get_public_ephemeral_key" // #nosec G101 + + // RPCMethodReplicateMasterSecret is the name of the `replicate_master_secret` method. + RPCMethodReplicateMasterSecret = "replicate_master_secret" + + // RPCMethodReplicateEphemeralSecret is the name of the `replicate_ephemeral_secret` method. + RPCMethodReplicateEphemeralSecret = "replicate_ephemeral_secret" + + // RPCMethodGenerateMasterSecret is the name of the `generate_master_secret` RPC method. + RPCMethodGenerateMasterSecret = "generate_master_secret" + + // RPCMethodGenerateEphemeralSecret is the name of the `generate_ephemeral_secret` RPC method. + RPCMethodGenerateEphemeralSecret = "generate_ephemeral_secret" + + // RPCMethodLoadMasterSecret is the name of the `load_master_secret` RPC method. + RPCMethodLoadMasterSecret = "load_master_secret" + + // RPCMethodLoadEphemeralSecret is the name of the `load_ephemeral_secret` RPC method. + RPCMethodLoadEphemeralSecret = "load_ephemeral_secret" + + // initResponseSignatureContext is the context used to sign key manager init responses. + initResponseSignatureContext = signature.NewContext("oasis-core/keymanager: init response") +) + +const ( + // GasOpUpdatePolicy is the gas operation identifier for policy updates + // costs. + GasOpUpdatePolicy transaction.Op = "update_policy" + // GasOpPublishMasterSecret is the gas operation identifier for publishing + // key manager master secret. + GasOpPublishMasterSecret transaction.Op = "publish_master_secret" + // GasOpPublishEphemeralSecret is the gas operation identifier for publishing + // key manager ephemeral secret. + GasOpPublishEphemeralSecret transaction.Op = "publish_ephemeral_secret" +) + +// XXX: Define reasonable default gas costs. + +// DefaultGasCosts are the "default" gas costs for operations. +var DefaultGasCosts = transaction.Costs{ + GasOpUpdatePolicy: 1000, + GasOpPublishMasterSecret: 1000, + GasOpPublishEphemeralSecret: 1000, +} + +// KeyPairID is a 256-bit key pair identifier. +type KeyPairID [KeyPairIDSize]byte + +// Status is the current key manager status. +type Status struct { + // ID is the runtime ID of the key manager. + ID common.Namespace `json:"id"` + + // IsInitialized is true iff the key manager is done initializing. + IsInitialized bool `json:"is_initialized"` + + // IsSecure is true iff the key manager is secure. + IsSecure bool `json:"is_secure"` + + // Generation is the generation of the latest master secret. + Generation uint64 `json:"generation,omitempty"` + + // RotationEpoch is the epoch of the last master secret rotation. + RotationEpoch beacon.EpochTime `json:"rotation_epoch,omitempty"` + + // Checksum is the key manager master secret verification checksum. + Checksum []byte `json:"checksum"` + + // Nodes is the list of currently active key manager node IDs. + Nodes []signature.PublicKey `json:"nodes"` + + // Policy is the key manager policy. + Policy *SignedPolicySGX `json:"policy"` + + // RSK is the runtime signing key of the key manager. + RSK *signature.PublicKey `json:"rsk,omitempty"` +} + +// NextGeneration returns the generation of the next master secret. +func (s *Status) NextGeneration() uint64 { + if len(s.Checksum) == 0 { + return 0 + } + return s.Generation + 1 +} + +// VerifyRotationEpoch verifies if rotation can be performed in the given epoch. +func (s *Status) VerifyRotationEpoch(epoch beacon.EpochTime) error { + if nextGen := s.NextGeneration(); nextGen == 0 { + return nil + } + + // By default, rotation is disabled unless specified in the policy. + var rotationInterval beacon.EpochTime + if s.Policy != nil { + rotationInterval = s.Policy.Policy.MasterSecretRotationInterval + } + + // Reject if rotation is disabled. + if rotationInterval == 0 { + return fmt.Errorf("master secret rotation disabled") + } + + // Reject if the rotation period has not expired. + rotationEpoch := s.RotationEpoch + rotationInterval + if epoch < rotationEpoch { + return fmt.Errorf("master secret rotation interval has not yet expired") + } + + return nil +} + +// Backend is a key manager management implementation. +type Backend interface { + // GetStatus returns a key manager status by key manager ID. + GetStatus(context.Context, *registry.NamespaceQuery) (*Status, error) + + // GetStatuses returns all currently tracked key manager statuses. + GetStatuses(context.Context, int64) ([]*Status, error) + + // WatchStatuses returns a channel that produces a stream of messages + // containing the key manager statuses as it changes over time. + // + // Upon subscription the current status is sent immediately. + WatchStatuses() (<-chan *Status, *pubsub.Subscription) + + // StateToGenesis returns the genesis state at specified block height. + StateToGenesis(context.Context, int64) (*Genesis, error) + + // GetMasterSecret returns the key manager master secret. + GetMasterSecret(context.Context, *registry.NamespaceQuery) (*SignedEncryptedMasterSecret, error) + + // WatchMasterSecrets returns a channel that produces a stream of master secrets. + WatchMasterSecrets() (<-chan *SignedEncryptedMasterSecret, *pubsub.Subscription) + + // GetEphemeralSecret returns the key manager ephemeral secret. + GetEphemeralSecret(context.Context, *registry.NamespaceQuery) (*SignedEncryptedEphemeralSecret, error) + + // WatchEphemeralSecrets returns a channel that produces a stream of ephemeral secrets. + WatchEphemeralSecrets() (<-chan *SignedEncryptedEphemeralSecret, *pubsub.Subscription) +} + +// NewUpdatePolicyTx creates a new policy update transaction. +func NewUpdatePolicyTx(nonce uint64, fee *transaction.Fee, sigPol *SignedPolicySGX) *transaction.Transaction { + return transaction.NewTransaction(nonce, fee, MethodUpdatePolicy, sigPol) +} + +// NewPublishMasterSecretTx creates a new publish master secret transaction. +func NewPublishMasterSecretTx(nonce uint64, fee *transaction.Fee, sigSec *SignedEncryptedMasterSecret) *transaction.Transaction { + return transaction.NewTransaction(nonce, fee, MethodPublishMasterSecret, sigSec) +} + +// NewPublishEphemeralSecretTx creates a new publish ephemeral secret transaction. +func NewPublishEphemeralSecretTx(nonce uint64, fee *transaction.Fee, sigSec *SignedEncryptedEphemeralSecret) *transaction.Transaction { + return transaction.NewTransaction(nonce, fee, MethodPublishEphemeralSecret, sigSec) +} + +// InitRequest is the initialization RPC request, sent to the key manager +// enclave. +type InitRequest struct { + Status Status `json:"status,omitempty"` +} + +// InitResponse is the initialization RPC response, returned as part of a +// SignedInitResponse from the key manager enclave. +type InitResponse struct { + IsSecure bool `json:"is_secure"` + Checksum []byte `json:"checksum"` + NextChecksum []byte `json:"next_checksum,omitempty"` + PolicyChecksum []byte `json:"policy_checksum"` + RSK *signature.PublicKey `json:"rsk,omitempty"` + NextRSK *signature.PublicKey `json:"next_rsk,omitempty"` +} + +// SignedInitResponse is the signed initialization RPC response, returned +// from the key manager enclave. +type SignedInitResponse struct { + InitResponse InitResponse `json:"init_response"` + Signature []byte `json:"signature"` +} + +// Verify verifies the signature of the init response using the given key. +func (r *SignedInitResponse) Verify(pk signature.PublicKey) error { + raw := cbor.Marshal(r.InitResponse) + if !pk.Verify(initResponseSignatureContext, raw, r.Signature) { + return fmt.Errorf("keymanager: invalid initialization response signature") + } + return nil +} + +// SignInitResponse signs the given init response. +func SignInitResponse(signer signature.Signer, response *InitResponse) (*SignedInitResponse, error) { + sig, err := signer.ContextSign(initResponseSignatureContext, cbor.Marshal(response)) + if err != nil { + return nil, err + } + return &SignedInitResponse{ + InitResponse: *response, + Signature: sig, + }, nil +} + +// LongTermKeyRequest is the long-term key RPC request, sent to the key manager +// enclave. +type LongTermKeyRequest struct { + Height *uint64 `json:"height"` + ID common.Namespace `json:"runtime_id"` + KeyPairID KeyPairID `json:"key_pair_id"` + Generation uint64 `json:"generation"` +} + +// EphemeralKeyRequest is the ephemeral key RPC request, sent to the key manager +// enclave. +type EphemeralKeyRequest struct { + Height *uint64 `json:"height"` + ID common.Namespace `json:"runtime_id"` + KeyPairID KeyPairID `json:"key_pair_id"` + Epoch beacon.EpochTime `json:"epoch"` +} + +// SignedPublicKey is the RPC response, returned as part of +// an EphemeralKeyRequest from the key manager enclave. +type SignedPublicKey struct { + Key x25519.PublicKey `json:"key"` + Checksum []byte `json:"checksum"` + Signature signature.RawSignature `json:"signature"` + Expiration *beacon.EpochTime `json:"expiration,omitempty"` +} + +// GenerateMasterSecretRequest is the generate master secret RPC request, +// sent to the key manager enclave. +type GenerateMasterSecretRequest struct { + Generation uint64 `json:"generation"` + Epoch beacon.EpochTime `json:"epoch"` +} + +// GenerateMasterSecretResponse is the RPC response, returned as part of +// a GenerateMasterSecretRequest from the key manager enclave. +type GenerateMasterSecretResponse struct { + SignedSecret SignedEncryptedMasterSecret `json:"signed_secret"` +} + +// GenerateEphemeralSecretRequest is the generate ephemeral secret RPC request, +// sent to the key manager enclave. +type GenerateEphemeralSecretRequest struct { + Epoch beacon.EpochTime `json:"epoch"` +} + +// GenerateEphemeralSecretResponse is the RPC response, returned as part of +// a GenerateEphemeralSecretRequest from the key manager enclave. +type GenerateEphemeralSecretResponse struct { + SignedSecret SignedEncryptedEphemeralSecret `json:"signed_secret"` +} + +// LoadMasterSecretRequest is the load master secret RPC request, +// sent to the key manager enclave. +type LoadMasterSecretRequest struct { + SignedSecret SignedEncryptedMasterSecret `json:"signed_secret"` +} + +// LoadEphemeralSecretRequest is the load ephemeral secret RPC request, +// sent to the key manager enclave. +type LoadEphemeralSecretRequest struct { + SignedSecret SignedEncryptedEphemeralSecret `json:"signed_secret"` +} + +// Genesis is the key manager management genesis state. +type Genesis struct { + // Parameters are the key manager consensus parameters. + Parameters ConsensusParameters `json:"params"` + + Statuses []*Status `json:"statuses,omitempty"` +} + +// ConsensusParameters are the key manager consensus parameters. +type ConsensusParameters struct { + GasCosts transaction.Costs `json:"gas_costs,omitempty"` +} + +// ConsensusParameterChanges are allowed key manager consensus parameter changes. +type ConsensusParameterChanges struct { + // GasCosts are the new gas costs. + GasCosts transaction.Costs `json:"gas_costs,omitempty"` +} + +// Apply applies changes to the given consensus parameters. +func (c *ConsensusParameterChanges) Apply(params *ConsensusParameters) error { + if c.GasCosts != nil { + params.GasCosts = c.GasCosts + } + return nil +} + +// StatusUpdateEvent is the keymanager status update event. +type StatusUpdateEvent struct { + Statuses []*Status +} + +// EventKind returns a string representation of this event's kind. +func (ev *StatusUpdateEvent) EventKind() string { + return "status" +} + +// MasterSecretPublishedEvent is the key manager master secret published event. +type MasterSecretPublishedEvent struct { + Secret *SignedEncryptedMasterSecret +} + +// EventKind returns a string representation of this event's kind. +func (ev *MasterSecretPublishedEvent) EventKind() string { + return "master_secret" +} + +// EphemeralSecretPublishedEvent is the key manager ephemeral secret published event. +type EphemeralSecretPublishedEvent struct { + Secret *SignedEncryptedEphemeralSecret +} + +// EventKind returns a string representation of this event's kind. +func (ev *EphemeralSecretPublishedEvent) EventKind() string { + return "ephemeral_secret" +} diff --git a/go/keymanager/api/api_test.go b/go/keymanager/secrets/api_test.go similarity index 98% rename from go/keymanager/api/api_test.go rename to go/keymanager/secrets/api_test.go index 965bbc751d1..ba41a2421f3 100644 --- a/go/keymanager/api/api_test.go +++ b/go/keymanager/secrets/api_test.go @@ -1,4 +1,4 @@ -package api +package secrets import ( "testing" diff --git a/go/keymanager/secrets/grpc.go b/go/keymanager/secrets/grpc.go new file mode 100644 index 00000000000..c48516048f0 --- /dev/null +++ b/go/keymanager/secrets/grpc.go @@ -0,0 +1,392 @@ +package secrets + +import ( + "context" + + "google.golang.org/grpc" + + cmnGrpc "github.com/oasisprotocol/oasis-core/go/common/grpc" + "github.com/oasisprotocol/oasis-core/go/common/pubsub" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" +) + +var ( + // serviceName is the gRPC service name. + serviceName = cmnGrpc.NewServiceName("KeyManager") + + // methodGetStatus is the GetStatus method. + methodGetStatus = serviceName.NewMethod("GetStatus", registry.NamespaceQuery{}) + // methodGetStatuses is the GetStatuses method. + methodGetStatuses = serviceName.NewMethod("GetStatuses", int64(0)) + // methodGetMasterSecret is the GetMasterSecret method. + methodGetMasterSecret = serviceName.NewMethod("GetMasterSecret", registry.NamespaceQuery{}) + // methodGetEphemeralSecret is the GetEphemeralSecret method. + methodGetEphemeralSecret = serviceName.NewMethod("GetEphemeralSecret", registry.NamespaceQuery{}) + + // methodWatchStatuses is the WatchStatuses method. + methodWatchStatuses = serviceName.NewMethod("WatchStatuses", nil) + // methodWatchMasterSecrets is the WatchMasterSecrets method. + methodWatchMasterSecrets = serviceName.NewMethod("WatchMasterSecrets", nil) + // methodWatchEphemeralSecrets is the WatchEphemeralSecrets method. + methodWatchEphemeralSecrets = serviceName.NewMethod("WatchEphemeralSecrets", nil) + + // serviceDesc is the gRPC service descriptor. + serviceDesc = grpc.ServiceDesc{ + ServiceName: string(serviceName), + HandlerType: (*Backend)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: methodGetStatus.ShortName(), + Handler: handlerGetStatus, + }, + { + MethodName: methodGetStatuses.ShortName(), + Handler: handlerGetStatuses, + }, + { + MethodName: methodGetMasterSecret.ShortName(), + Handler: handlerGetMasterSecret, + }, + { + MethodName: methodGetEphemeralSecret.ShortName(), + Handler: handlerGetEphemeralSecret, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: methodWatchStatuses.ShortName(), + Handler: handlerWatchStatuses, + ServerStreams: true, + }, + { + StreamName: methodWatchMasterSecrets.ShortName(), + Handler: handlerWatchMasterSecrets, + ServerStreams: true, + }, + { + StreamName: methodWatchEphemeralSecrets.ShortName(), + Handler: handlerWatchEphemeralSecrets, + ServerStreams: true, + }, + }, + } +) + +func handlerGetStatus( + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var query registry.NamespaceQuery + if err := dec(&query); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).GetStatus(ctx, &query) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodGetStatus.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).GetStatus(ctx, req.(*registry.NamespaceQuery)) + } + return interceptor(ctx, &query, info, handler) +} + +func handlerGetStatuses( + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var height int64 + if err := dec(&height); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).GetStatuses(ctx, height) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodGetStatuses.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).GetStatuses(ctx, req.(int64)) + } + return interceptor(ctx, height, info, handler) +} + +func handlerGetMasterSecret( + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var query registry.NamespaceQuery + if err := dec(&query); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).GetMasterSecret(ctx, &query) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodGetMasterSecret.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).GetMasterSecret(ctx, req.(*registry.NamespaceQuery)) + } + return interceptor(ctx, &query, info, handler) +} + +func handlerGetEphemeralSecret( + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var query registry.NamespaceQuery + if err := dec(&query); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).GetEphemeralSecret(ctx, &query) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodGetEphemeralSecret.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).GetEphemeralSecret(ctx, req.(*registry.NamespaceQuery)) + } + return interceptor(ctx, &query, info, handler) +} + +func handlerWatchStatuses(srv interface{}, stream grpc.ServerStream) error { + if err := stream.RecvMsg(nil); err != nil { + return err + } + + ctx := stream.Context() + ch, sub := srv.(Backend).WatchStatuses() + defer sub.Close() + + for { + select { + case stat, ok := <-ch: + if !ok { + return nil + } + + if err := stream.SendMsg(stat); err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func handlerWatchMasterSecrets(srv interface{}, stream grpc.ServerStream) error { + if err := stream.RecvMsg(nil); err != nil { + return err + } + + ctx := stream.Context() + ch, sub := srv.(Backend).WatchMasterSecrets() + defer sub.Close() + + for { + select { + case sec, ok := <-ch: + if !ok { + return nil + } + + if err := stream.SendMsg(sec); err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func handlerWatchEphemeralSecrets(srv interface{}, stream grpc.ServerStream) error { + if err := stream.RecvMsg(nil); err != nil { + return err + } + + ctx := stream.Context() + ch, sub := srv.(Backend).WatchEphemeralSecrets() + defer sub.Close() + + for { + select { + case sec, ok := <-ch: + if !ok { + return nil + } + + if err := stream.SendMsg(sec); err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +// RegisterService registers a new keymanager secrets backend service with the given gRPC server. +func RegisterService(server *grpc.Server, service Backend) { + server.RegisterService(&serviceDesc, service) +} + +// Client is a gRPC keymanager secrets client. +type Client struct { + conn *grpc.ClientConn +} + +func (c *Client) GetStatus(ctx context.Context, query *registry.NamespaceQuery) (*Status, error) { + var resp Status + if err := c.conn.Invoke(ctx, methodGetStatus.FullName(), query, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) GetStatuses(ctx context.Context, height int64) ([]*Status, error) { + var resp []*Status + if err := c.conn.Invoke(ctx, methodGetStatuses.FullName(), height, &resp); err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) GetMasterSecret(ctx context.Context, query *registry.NamespaceQuery) (*SignedEncryptedMasterSecret, error) { + var resp *SignedEncryptedMasterSecret + if err := c.conn.Invoke(ctx, methodGetMasterSecret.FullName(), query, &resp); err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) GetEphemeralSecret(ctx context.Context, query *registry.NamespaceQuery) (*SignedEncryptedEphemeralSecret, error) { + var resp *SignedEncryptedEphemeralSecret + if err := c.conn.Invoke(ctx, methodGetEphemeralSecret.FullName(), query, &resp); err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) WatchStatuses(ctx context.Context) (<-chan *Status, pubsub.ClosableSubscription, error) { + ctx, sub := pubsub.NewContextSubscription(ctx) + + stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchStatuses.FullName()) + if err != nil { + return nil, nil, err + } + if err = stream.SendMsg(nil); err != nil { + return nil, nil, err + } + if err = stream.CloseSend(); err != nil { + return nil, nil, err + } + + ch := make(chan *Status) + go func() { + defer close(ch) + + for { + var stat Status + if serr := stream.RecvMsg(&stat); serr != nil { + return + } + + select { + case ch <- &stat: + case <-ctx.Done(): + return + } + } + }() + + return ch, sub, nil +} + +func (c *Client) WatchMasterSecrets(ctx context.Context) (<-chan *SignedEncryptedMasterSecret, pubsub.ClosableSubscription, error) { + ctx, sub := pubsub.NewContextSubscription(ctx) + + stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchMasterSecrets.FullName()) + if err != nil { + return nil, nil, err + } + if err = stream.SendMsg(nil); err != nil { + return nil, nil, err + } + if err = stream.CloseSend(); err != nil { + return nil, nil, err + } + + ch := make(chan *SignedEncryptedMasterSecret) + go func() { + defer close(ch) + + for { + var sec SignedEncryptedMasterSecret + if serr := stream.RecvMsg(&sec); serr != nil { + return + } + + select { + case ch <- &sec: + case <-ctx.Done(): + return + } + } + }() + + return ch, sub, nil +} + +func (c *Client) WatchEphemeralSecrets(ctx context.Context) (<-chan *SignedEncryptedEphemeralSecret, pubsub.ClosableSubscription, error) { + ctx, sub := pubsub.NewContextSubscription(ctx) + + stream, err := c.conn.NewStream(ctx, &serviceDesc.Streams[0], methodWatchEphemeralSecrets.FullName()) + if err != nil { + return nil, nil, err + } + if err = stream.SendMsg(nil); err != nil { + return nil, nil, err + } + if err = stream.CloseSend(); err != nil { + return nil, nil, err + } + + ch := make(chan *SignedEncryptedEphemeralSecret) + go func() { + defer close(ch) + + for { + var sec SignedEncryptedEphemeralSecret + if serr := stream.RecvMsg(&sec); serr != nil { + return + } + + select { + case ch <- &sec: + case <-ctx.Done(): + return + } + } + }() + + return ch, sub, nil +} + +// NewClient creates a new gRPC keymanager secrets client service. +func NewClient(c *grpc.ClientConn) *Client { + return &Client{c} +} diff --git a/go/keymanager/api/policy_sgx.go b/go/keymanager/secrets/policy_sgx.go similarity index 99% rename from go/keymanager/api/policy_sgx.go rename to go/keymanager/secrets/policy_sgx.go index 7d9a9d55ed9..508ca272c35 100644 --- a/go/keymanager/api/policy_sgx.go +++ b/go/keymanager/secrets/policy_sgx.go @@ -1,4 +1,4 @@ -package api +package secrets import ( "fmt" diff --git a/go/keymanager/api/sanity_check.go b/go/keymanager/secrets/sanity_check.go similarity index 98% rename from go/keymanager/api/sanity_check.go rename to go/keymanager/secrets/sanity_check.go index b39f493dae6..b759a94b90d 100644 --- a/go/keymanager/api/sanity_check.go +++ b/go/keymanager/secrets/sanity_check.go @@ -1,4 +1,4 @@ -package api +package secrets import ( "fmt" diff --git a/go/keymanager/api/secret.go b/go/keymanager/secrets/secret.go similarity index 99% rename from go/keymanager/api/secret.go rename to go/keymanager/secrets/secret.go index dc935ccc25f..7cc9d493425 100644 --- a/go/keymanager/api/secret.go +++ b/go/keymanager/secrets/secret.go @@ -1,4 +1,4 @@ -package api +package secrets import ( "fmt" diff --git a/go/keymanager/api/secret_test.go b/go/keymanager/secrets/secret_test.go similarity index 99% rename from go/keymanager/api/secret_test.go rename to go/keymanager/secrets/secret_test.go index c43bcac4535..a1559ab445b 100644 --- a/go/keymanager/api/secret_test.go +++ b/go/keymanager/secrets/secret_test.go @@ -1,4 +1,4 @@ -package api +package secrets import ( "crypto/sha512" diff --git a/go/oasis-node/cmd/debug/dumpdb/dumpdb.go b/go/oasis-node/cmd/debug/dumpdb/dumpdb.go index 61b2e487af8..2ea0d0cf683 100644 --- a/go/oasis-node/cmd/debug/dumpdb/dumpdb.go +++ b/go/oasis-node/cmd/debug/dumpdb/dumpdb.go @@ -314,7 +314,7 @@ func dumpKeyManager(ctx context.Context, qs *dumpQueryState) (*keymanager.Genesi if err != nil { return nil, fmt.Errorf("dumpdb: failed to create key manager query: %w", err) } - st, err := q.Genesis(ctx) + st, err := q.Secrets().Genesis(ctx) if err != nil { return nil, fmt.Errorf("dumpdb: failed to dump key manager state: %w", err) } diff --git a/go/oasis-node/cmd/genesis/genesis.go b/go/oasis-node/cmd/genesis/genesis.go index 76f9653a71c..840e29bc368 100644 --- a/go/oasis-node/cmd/genesis/genesis.go +++ b/go/oasis-node/cmd/genesis/genesis.go @@ -32,7 +32,7 @@ import ( genesis "github.com/oasisprotocol/oasis-core/go/genesis/api" genesisFile "github.com/oasisprotocol/oasis-core/go/genesis/file" governance "github.com/oasisprotocol/oasis-core/go/governance/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" cmdCmnGenesis "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/genesis" @@ -556,9 +556,9 @@ func AppendRootHashState(doc *genesis.Document, exports []string, l *logging.Log // AppendKeyManagerState appends the key manager genesis state given a vector of // key manager statuses. func AppendKeyManagerState(doc *genesis.Document, statuses []string, l *logging.Logger) error { - kmSt := keymanager.Genesis{ - Parameters: keymanager.ConsensusParameters{ - GasCosts: keymanager.DefaultGasCosts, // TODO: Make these configurable. + kmSt := secrets.Genesis{ + Parameters: secrets.ConsensusParameters{ + GasCosts: secrets.DefaultGasCosts, // TODO: Make these configurable. }, } @@ -572,7 +572,7 @@ func AppendKeyManagerState(doc *genesis.Document, statuses []string, l *logging. return err } - var status keymanager.Status + var status secrets.Status if err = json.Unmarshal(b, &status); err != nil { l.Error("failed to parse genesis key manager status", "err", err, diff --git a/go/oasis-node/cmd/genesis/migrate.go b/go/oasis-node/cmd/genesis/migrate.go index 22de7fedc7a..a5830a7086c 100644 --- a/go/oasis-node/cmd/genesis/migrate.go +++ b/go/oasis-node/cmd/genesis/migrate.go @@ -17,7 +17,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/entity" "github.com/oasisprotocol/oasis-core/go/common/node" genesis "github.com/oasisprotocol/oasis-core/go/genesis/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" registry "github.com/oasisprotocol/oasis-core/go/registry/api" @@ -306,8 +306,8 @@ NodeLoop: } // Move key manager gas costs from the registry to the key manager. - newDoc.KeyManager.Parameters.GasCosts = keymanager.DefaultGasCosts - newDoc.KeyManager.Parameters.GasCosts[keymanager.GasOpUpdatePolicy] = newDoc.Registry.Parameters.GasCosts["update_keymanager"] + newDoc.KeyManager.Parameters.GasCosts = secrets.DefaultGasCosts + newDoc.KeyManager.Parameters.GasCosts[secrets.GasOpUpdatePolicy] = newDoc.Registry.Parameters.GasCosts["update_keymanager"] delete(newDoc.Registry.Parameters.GasCosts, "update_keymanager") // Update registry parameters. diff --git a/go/oasis-node/cmd/keymanager/keymanager.go b/go/oasis-node/cmd/keymanager/keymanager.go index 82b9bcf06b8..025b28bab39 100644 --- a/go/oasis-node/cmd/keymanager/keymanager.go +++ b/go/oasis-node/cmd/keymanager/keymanager.go @@ -21,6 +21,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/logging" "github.com/oasisprotocol/oasis-core/go/common/sgx" kmApi "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" cmdConsensus "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/consensus" cmdContext "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/context" @@ -122,7 +123,7 @@ func doInitPolicy(*cobra.Command, []string) { ) } -func policyFromFlags() (*kmApi.PolicySGX, error) { +func policyFromFlags() (*secrets.PolicySGX, error) { var id common.Namespace if err := id.UnmarshalHex(viper.GetString(CfgPolicyID)); err != nil { logger.Error("failed to parse key manager runtime ID", @@ -134,7 +135,7 @@ func policyFromFlags() (*kmApi.PolicySGX, error) { serial := viper.GetUint32(CfgPolicySerial) - enclaves := make(map[sgx.EnclaveIdentity]*kmApi.EnclavePolicySGX) + enclaves := make(map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX) // Replicate and query permissions are set per-key manager enclave ID. // Since viper doesn't store order of arguments, go through os.Args by hand, @@ -150,7 +151,7 @@ func policyFromFlags() (*kmApi.PolicySGX, error) { return nil, err } - enclaves[kmEnclaveID] = &kmApi.EnclavePolicySGX{ + enclaves[kmEnclaveID] = &secrets.EnclavePolicySGX{ MayReplicate: []sgx.EnclaveIdentity{}, MayQuery: make(map[common.Namespace][]sgx.EnclaveIdentity), } @@ -211,7 +212,7 @@ func policyFromFlags() (*kmApi.PolicySGX, error) { rotationInterval := api.EpochTime(viper.GetUint64(CfgPolicyMasterSecretRotationInterval)) - return &kmApi.PolicySGX{ + return &secrets.PolicySGX{ Serial: serial, ID: id, Enclaves: enclaves, @@ -284,7 +285,7 @@ func signPolicyFromFlags() (*signature.Signature, error) { return nil, err } - rawSigBytes, err := signer.ContextSign(kmApi.PolicySGXSignatureContext, policyBytes) + rawSigBytes, err := signer.ContextSign(secrets.PolicySGXSignatureContext, policyBytes) if err != nil { return nil, err } @@ -349,7 +350,7 @@ func verifyPolicyFromFlags() error { return err } - if !s.Verify(kmApi.PolicySGXSignatureContext, policyBytes) { + if !s.Verify(secrets.PolicySGXSignatureContext, policyBytes) { return errors.New("signature is not valid for given policy") } } @@ -358,9 +359,9 @@ func verifyPolicyFromFlags() error { return nil } -// / unmarshalPolicyChor checks whether given CBOR is a valid kmApi.PolicySGX struct. -func unmarshalPolicyCBOR(pb []byte) (*kmApi.PolicySGX, error) { - var p *kmApi.PolicySGX = &kmApi.PolicySGX{} +// / unmarshalPolicyChor checks whether given CBOR is a valid secrets.PolicySGX struct. +func unmarshalPolicyCBOR(pb []byte) (*secrets.PolicySGX, error) { + var p *secrets.PolicySGX = &secrets.PolicySGX{} if err := cbor.Unmarshal(pb, p); err != nil { return nil, err } @@ -417,7 +418,7 @@ func doGenUpdate(*cobra.Command, []string) { // Assemble the SignedPolicySGX from the policy document and detached // signatures. - var signedPolicy kmApi.SignedPolicySGX + var signedPolicy secrets.SignedPolicySGX policyBytes, err := os.ReadFile(viper.GetString(CfgPolicyFile)) if err != nil { @@ -455,7 +456,7 @@ func doGenUpdate(*cobra.Command, []string) { } // Validate the SignedPolicySGX. - if err = kmApi.SanityCheckSignedPolicySGX(nil, &signedPolicy); err != nil { + if err = secrets.SanityCheckSignedPolicySGX(nil, &signedPolicy); err != nil { logger.Error("failed to validate SignedPolicySGX", "err", err, ) @@ -464,11 +465,11 @@ func doGenUpdate(*cobra.Command, []string) { // Build, sign, and write the UpdatePolicy transaction. nonce, fee := cmdConsensus.GetTxNonceAndFee() - tx := kmApi.NewUpdatePolicyTx(nonce, fee, &signedPolicy) + tx := secrets.NewUpdatePolicyTx(nonce, fee, &signedPolicy) cmdConsensus.SignAndSaveTx(cmdContext.GetCtxWithGenesisInfo(genesis), tx, nil) } -func statusFromFlags() (*kmApi.Status, error) { +func statusFromFlags() (*secrets.Status, error) { var id common.Namespace if err := id.UnmarshalHex(viper.GetString(CfgStatusID)); err != nil { logger.Error("failed to parse key manager status ID", @@ -479,7 +480,7 @@ func statusFromFlags() (*kmApi.Status, error) { } // Unmarshal KM policy and its signatures. - var signedPolicy *kmApi.SignedPolicySGX + var signedPolicy *secrets.SignedPolicySGX if viper.GetString(CfgPolicyFile) != "" { pb, err := os.ReadFile(viper.GetString(CfgPolicyFile)) if err != nil { @@ -490,7 +491,7 @@ func statusFromFlags() (*kmApi.Status, error) { if err != nil { return nil, err } - signedPolicy = &kmApi.SignedPolicySGX{ + signedPolicy = &secrets.SignedPolicySGX{ Policy: *p, } @@ -516,8 +517,8 @@ func statusFromFlags() (*kmApi.Status, error) { if err != nil { return nil, err } - if len(checksum) != kmApi.ChecksumSize { - return nil, fmt.Errorf("checksum %x is not %d bytes long", checksum, kmApi.ChecksumSize) + if len(checksum) != secrets.ChecksumSize { + return nil, fmt.Errorf("checksum %x is not %d bytes long", checksum, secrets.ChecksumSize) } } @@ -542,7 +543,7 @@ func statusFromFlags() (*kmApi.Status, error) { return nil, fmt.Errorf("%s provided, but %s is false", CfgStatusRSK, CfgStatusInitialized) } - return &kmApi.Status{ + return &secrets.Status{ ID: id, IsInitialized: viper.GetBool(CfgStatusInitialized), IsSecure: viper.GetBool(CfgStatusSecure), diff --git a/go/oasis-test-runner/oasis/cli/keymanager.go b/go/oasis-test-runner/oasis/cli/keymanager.go index 35b42178508..a91b670db05 100644 --- a/go/oasis-test-runner/oasis/cli/keymanager.go +++ b/go/oasis-test-runner/oasis/cli/keymanager.go @@ -8,7 +8,7 @@ import ( beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/sgx" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" cmdConsensus "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/consensus" "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" @@ -21,7 +21,7 @@ type KeymanagerHelpers struct { } // InitPolicy generates the KM policy file. -func (k *KeymanagerHelpers) InitPolicy(runtimeID common.Namespace, serial uint32, rotationInterval beacon.EpochTime, policies map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX, polPath string) error { +func (k *KeymanagerHelpers) InitPolicy(runtimeID common.Namespace, serial uint32, rotationInterval beacon.EpochTime, policies map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX, polPath string) error { k.logger.Info("initing KM policy", "policy_path", polPath, "serial", serial, diff --git a/go/oasis-test-runner/scenario/e2e/runtime/helpers_keymanager.go b/go/oasis-test-runner/scenario/e2e/runtime/helpers_keymanager.go index 0ccf77bcfcd..c87d7199093 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/helpers_keymanager.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/helpers_keymanager.go @@ -14,7 +14,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/sgx" "github.com/oasisprotocol/oasis-core/go/common/version" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis/cli" @@ -22,42 +22,42 @@ import ( ) // KeyManagerStatus returns the latest key manager status. -func (sc *Scenario) KeyManagerStatus(ctx context.Context) (*keymanager.Status, error) { - return sc.Net.Controller().Keymanager.GetStatus(ctx, ®istry.NamespaceQuery{ +func (sc *Scenario) KeyManagerStatus(ctx context.Context) (*secrets.Status, error) { + return sc.Net.Controller().Keymanager.Secrets().GetStatus(ctx, ®istry.NamespaceQuery{ Height: consensus.HeightLatest, ID: KeyManagerRuntimeID, }) } // MasterSecret returns the key manager master secret. -func (sc *Scenario) MasterSecret(ctx context.Context) (*keymanager.SignedEncryptedMasterSecret, error) { - secret, err := sc.Net.Controller().Keymanager.GetMasterSecret(ctx, ®istry.NamespaceQuery{ +func (sc *Scenario) MasterSecret(ctx context.Context) (*secrets.SignedEncryptedMasterSecret, error) { + secret, err := sc.Net.Controller().Keymanager.Secrets().GetMasterSecret(ctx, ®istry.NamespaceQuery{ Height: consensus.HeightLatest, ID: KeyManagerRuntimeID, }) - if err == keymanager.ErrNoSuchMasterSecret { + if err == secrets.ErrNoSuchMasterSecret { return nil, nil } return secret, err } // WaitMasterSecret waits until the specified generation of the master secret is generated. -func (sc *Scenario) WaitMasterSecret(ctx context.Context, generation uint64) (*keymanager.Status, error) { +func (sc *Scenario) WaitMasterSecret(ctx context.Context, generation uint64) (*secrets.Status, error) { sc.Logger.Info("waiting for master secret", "generation", generation) - mstCh, mstSub, err := sc.Net.Controller().Keymanager.WatchMasterSecrets(ctx) + mstCh, mstSub, err := sc.Net.Controller().Keymanager.Secrets().WatchMasterSecrets(ctx) if err != nil { return nil, err } defer mstSub.Close() - stCh, stSub, err := sc.Net.Controller().Keymanager.WatchStatuses(ctx) + stCh, stSub, err := sc.Net.Controller().Keymanager.Secrets().WatchStatuses(ctx) if err != nil { return nil, err } defer stSub.Close() - var last *keymanager.Status + var last *secrets.Status for { select { case <-ctx.Done(): @@ -98,16 +98,16 @@ func (sc *Scenario) WaitMasterSecret(ctx context.Context, generation uint64) (*k } // WaitEphemeralSecrets waits for the specified number of ephemeral secrets to be generated. -func (sc *Scenario) WaitEphemeralSecrets(ctx context.Context, n int) (*keymanager.SignedEncryptedEphemeralSecret, error) { +func (sc *Scenario) WaitEphemeralSecrets(ctx context.Context, n int) (*secrets.SignedEncryptedEphemeralSecret, error) { sc.Logger.Info("waiting ephemeral secrets", "n", n) - ephCh, ephSub, err := sc.Net.Controller().Keymanager.WatchEphemeralSecrets(ctx) + ephCh, ephSub, err := sc.Net.Controller().Keymanager.Secrets().WatchEphemeralSecrets(ctx) if err != nil { return nil, err } defer ephSub.Close() - var secret *keymanager.SignedEncryptedEphemeralSecret + var secret *secrets.SignedEncryptedEphemeralSecret for i := 0; i < n; i++ { select { case secret = <-ephCh: @@ -128,11 +128,11 @@ func (sc *Scenario) UpdateRotationInterval(ctx context.Context, childEnv *env.En ) status, err := sc.KeyManagerStatus(ctx) - if err != nil && err != keymanager.ErrNoSuchStatus { + if err != nil && err != secrets.ErrNoSuchStatus { return err } - var policies map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX + var policies map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX if status != nil && status.Policy != nil { policies = status.Policy.Policy.Enclaves } @@ -211,7 +211,7 @@ func (sc *Scenario) CompareLongtermPublicKeys(ctx context.Context, idxs []int) e } // KeymanagerInitResponse returns InitResponse of the specified key manager node. -func (sc *Scenario) KeymanagerInitResponse(ctx context.Context, idx int) (*keymanager.InitResponse, error) { +func (sc *Scenario) KeymanagerInitResponse(ctx context.Context, idx int) (*secrets.InitResponse, error) { kms := sc.Net.Keymanagers() if kmLen := len(kms); kmLen <= idx { return nil, fmt.Errorf("expected more than %d keymanager, have: %v", idx, kmLen) @@ -237,7 +237,7 @@ func (sc *Scenario) KeymanagerInitResponse(ctx context.Context, idx int) (*keyma if rt == nil { return nil, fmt.Errorf("key manager is missing keymanager runtime from descriptor") } - var signedInitResponse keymanager.SignedInitResponse + var signedInitResponse secrets.SignedInitResponse if err = cbor.Unmarshal(rt.ExtraInfo, &signedInitResponse); err != nil { return nil, fmt.Errorf("failed to unmarshal extrainfo") } @@ -246,7 +246,7 @@ func (sc *Scenario) KeymanagerInitResponse(ctx context.Context, idx int) (*keyma } // UpdateEnclavePolicies updates enclave policies with a new runtime deployment. -func (sc *Scenario) UpdateEnclavePolicies(rt *oasis.Runtime, deploymentIndex int, policies map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX) { +func (sc *Scenario) UpdateEnclavePolicies(rt *oasis.Runtime, deploymentIndex int, policies map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX) { enclaveID := rt.GetEnclaveIdentity(deploymentIndex) if enclaveID == nil { return @@ -260,7 +260,7 @@ func (sc *Scenario) UpdateEnclavePolicies(rt *oasis.Runtime, deploymentIndex int } // Allow all runtimes to query the new key manager runtime. - newPolicy := keymanager.EnclavePolicySGX{ + newPolicy := secrets.EnclavePolicySGX{ MayQuery: make(map[common.Namespace][]sgx.EnclaveIdentity), MayReplicate: make([]sgx.EnclaveIdentity, 0), } @@ -288,10 +288,10 @@ func (sc *Scenario) UpdateEnclavePolicies(rt *oasis.Runtime, deploymentIndex int // - Each SGX runtime must have only one deployment and a distinct enclave identity. // - Key manager enclaves are not allowed to replicate the master secrets. // - All compute runtime enclaves are allowed to query key manager enclaves. -func (sc *Scenario) BuildAllEnclavePolicies() (map[common.Namespace]map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX, error) { +func (sc *Scenario) BuildAllEnclavePolicies() (map[common.Namespace]map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX, error) { sc.Logger.Info("building key manager SGX policy enclave policies map") - kmPolicies := make(map[common.Namespace]map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX) + kmPolicies := make(map[common.Namespace]map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX) // Each SGX runtime must have only one deployment. for _, rt := range sc.Net.Runtimes() { @@ -332,7 +332,7 @@ func (sc *Scenario) BuildAllEnclavePolicies() (map[common.Namespace]map[sgx.Encl return nil, fmt.Errorf("duplicate key manager runtime: %s", rt.ID()) } - kmPolicies[rt.ID()] = map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX{ + kmPolicies[rt.ID()] = map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX{ *enclaveID: { MayQuery: make(map[common.Namespace][]sgx.EnclaveIdentity), MayReplicate: make([]sgx.EnclaveIdentity, 0), @@ -370,7 +370,7 @@ func (sc *Scenario) BuildAllEnclavePolicies() (map[common.Namespace]map[sgx.Encl // // If the simple key manager runtime does not exist or is not running on an SGX platform, // it returns nil. -func (sc *Scenario) BuildEnclavePolicies() (map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX, error) { +func (sc *Scenario) BuildEnclavePolicies() (map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX, error) { policies, err := sc.BuildAllEnclavePolicies() if err != nil { return nil, err @@ -379,9 +379,9 @@ func (sc *Scenario) BuildEnclavePolicies() (map[sgx.EnclaveIdentity]*keymanager. } // ApplyKeyManagerPolicy applies the given policy to the simple key manager runtime. -func (sc *Scenario) ApplyKeyManagerPolicy(ctx context.Context, childEnv *env.Env, cli *cli.Helpers, rotationInterval beacon.EpochTime, policies map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX, nonce uint64) error { +func (sc *Scenario) ApplyKeyManagerPolicy(ctx context.Context, childEnv *env.Env, cli *cli.Helpers, rotationInterval beacon.EpochTime, policies map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX, nonce uint64) error { status, err := sc.KeyManagerStatus(ctx) - if err != nil && err != keymanager.ErrNoSuchStatus { + if err != nil && err != secrets.ErrNoSuchStatus { return err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/helpers_runtime.go b/go/oasis-test-runner/scenario/e2e/runtime/helpers_runtime.go index 6460ba21781..b41b4271a4f 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/helpers_runtime.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/helpers_runtime.go @@ -14,7 +14,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/sgx" "github.com/oasisprotocol/oasis-core/go/common/version" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis/cli" @@ -280,10 +280,10 @@ func (sc *Scenario) EnableRuntimeDeployment(ctx context.Context, childEnv *env.E // Update the key manager policy. status, err := sc.KeyManagerStatus(ctx) - if err != nil && err != keymanager.ErrNoSuchStatus { + if err != nil && err != secrets.ErrNoSuchStatus { return err } - var policies map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX + var policies map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX if status != nil && status.Policy != nil { policies = status.Policy.Policy.Enclaves } @@ -558,7 +558,7 @@ func (sc *Scenario) ensureReplicationWorked(ctx context.Context, km *oasis.Keyma if nodeRt == nil { return fmt.Errorf("node is missing keymanager runtime from descriptor") } - var signedInitResponse keymanager.SignedInitResponse + var signedInitResponse secrets.SignedInitResponse if err = cbor.Unmarshal(nodeRt.ExtraInfo, &signedInitResponse); err != nil { return fmt.Errorf("failed to unmarshal replica extrainfo") } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_client.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_client.go index cb60456102e..ba05a6e4558 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_client.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_client.go @@ -18,7 +18,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" p2p "github.com/oasisprotocol/oasis-core/go/p2p/api" "github.com/oasisprotocol/oasis-core/go/p2p/protocol" @@ -82,15 +82,15 @@ func (c *keyManagerRPCClient) addKeyManagerAddrToHost(km *oasis.Keymanager) (pee } func (c *keyManagerRPCClient) fetchPublicKey(ctx context.Context, generation uint64, peerID peer.ID) (*x25519.PublicKey, error) { - args := keymanager.LongTermKeyRequest{ + args := secrets.LongTermKeyRequest{ Height: nil, ID: KeyManagerRuntimeID, - KeyPairID: keymanager.KeyPairID{1, 2, 3}, + KeyPairID: secrets.KeyPairID{1, 2, 3}, Generation: generation, } req := enclaverpc.Request{ - Method: keymanager.RPCMethodGetPublicKey, + Method: secrets.RPCMethodGetPublicKey, Args: args, } @@ -118,7 +118,7 @@ func (c *keyManagerRPCClient) fetchPublicKey(ctx context.Context, generation uin return nil, fmt.Errorf(msg) } - var key keymanager.SignedPublicKey + var key secrets.SignedPublicKey if err = cbor.Unmarshal(rsp.Body.Success, &key); err != nil { return nil, err } @@ -127,15 +127,15 @@ func (c *keyManagerRPCClient) fetchPublicKey(ctx context.Context, generation uin } func (c *keyManagerRPCClient) fetchEphemeralPublicKey(ctx context.Context, epoch beacon.EpochTime, peerID peer.ID) (*x25519.PublicKey, error) { - args := keymanager.EphemeralKeyRequest{ + args := secrets.EphemeralKeyRequest{ Height: nil, ID: KeyManagerRuntimeID, - KeyPairID: keymanager.KeyPairID{1, 2, 3}, + KeyPairID: secrets.KeyPairID{1, 2, 3}, Epoch: epoch, } req := enclaverpc.Request{ - Method: keymanager.RPCMethodGetPublicEphemeralKey, + Method: secrets.RPCMethodGetPublicEphemeralKey, Args: args, } @@ -163,7 +163,7 @@ func (c *keyManagerRPCClient) fetchEphemeralPublicKey(ctx context.Context, epoch return nil, fmt.Errorf(msg) } - var key keymanager.SignedPublicKey + var key secrets.SignedPublicKey if err = cbor.Unmarshal(rsp.Body.Success, &key); err != nil { return nil, err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go index 0363bdd95b4..1041a09201d 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_ephemeral_secrets.go @@ -283,7 +283,7 @@ func (sc *kmEphemeralSecretsImpl) Run(ctx context.Context, _ *env.Env) error { / } // Test that published secrets are encrypted to all enclaves. - ephCh, ephSub, err := sc.Net.Controller().Keymanager.WatchEphemeralSecrets(ctx) + ephCh, ephSub, err := sc.Net.Controller().Keymanager.Secrets().WatchEphemeralSecrets(ctx) if err != nil { return err } @@ -497,7 +497,7 @@ func (sc *kmEphemeralSecretsImpl) submitKeyValueRuntimeDecryptTx( } func (sc *kmEphemeralSecretsImpl) checkNumberOfKeyManagers(ctx context.Context, n int) error { - status, err := sc.Net.Controller().Keymanager.GetStatus(ctx, ®istry.NamespaceQuery{ + status, err := sc.Net.Controller().Keymanager.Secrets().GetStatus(ctx, ®istry.NamespaceQuery{ Height: consensus.HeightLatest, ID: KeyManagerRuntimeID, }) diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go index 226aa405f6d..4097b40fa22 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_master_secrets.go @@ -6,7 +6,7 @@ import ( "fmt" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis/cli" @@ -205,13 +205,13 @@ func (sc *kmMasterSecretsImpl) monitorMasterSecrets(ctx context.Context) (func() // Monitor proposed secrets. go func() { - mstCh, mstSub, err := sc.Net.ClientController().Keymanager.WatchMasterSecrets(ctx) + mstCh, mstSub, err := sc.Net.ClientController().Keymanager.Secrets().WatchMasterSecrets(ctx) if err != nil { return } defer mstSub.Close() - var prev, next *keymanager.SignedEncryptedMasterSecret + var prev, next *secrets.SignedEncryptedMasterSecret for { select { case <-stopCh: @@ -262,13 +262,13 @@ func (sc *kmMasterSecretsImpl) monitorMasterSecrets(ctx context.Context) (func() // Monitor accepted secrets. go func() { - stCh, stSub, err := sc.Net.ClientController().Keymanager.WatchStatuses(ctx) + stCh, stSub, err := sc.Net.ClientController().Keymanager.Secrets().WatchStatuses(ctx) if err != nil { return } defer stSub.Close() - var prev, next *keymanager.Status + var prev, next *secrets.Status for { select { case <-stopCh: diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go index d915ad13008..f1e4342570e 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_replicate.go @@ -7,7 +7,7 @@ import ( "slices" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario" @@ -150,10 +150,10 @@ func (sc *kmReplicateImpl) Run(ctx context.Context, _ *env.Env) error { return sc.CompareLongtermPublicKeys(ctx, []int{0, 1, 2, 3}) } -func (sc *kmReplicateImpl) waitKeymanagerStatuses(ctx context.Context, n int) (*keymanager.Status, error) { +func (sc *kmReplicateImpl) waitKeymanagerStatuses(ctx context.Context, n int) (*secrets.Status, error) { sc.Logger.Info("waiting for key manager status", "n", n) - stCh, stSub, err := sc.Net.Controller().Keymanager.WatchStatuses(ctx) + stCh, stSub, err := sc.Net.Controller().Keymanager.Secrets().WatchStatuses(ctx) if err != nil { return nil, err } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go index 0d01969e390..272456390a2 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_rotation_failure.go @@ -227,7 +227,7 @@ func (sc *kmRotationFailureImpl) extendKeymanagerRegistrations(ctx context.Conte } func (sc *kmRotationFailureImpl) verifyMasterSecretRejections(ctx context.Context, n int) error { - mstCh, mstSub, err := sc.Net.Controller().Keymanager.WatchMasterSecrets(ctx) + mstCh, mstSub, err := sc.Net.Controller().Keymanager.Secrets().WatchMasterSecrets(ctx) if err != nil { return err } diff --git a/go/runtime/host/protocol/types.go b/go/runtime/host/protocol/types.go index d14870d6820..55311c5121f 100644 --- a/go/runtime/host/protocol/types.go +++ b/go/runtime/host/protocol/types.go @@ -18,7 +18,7 @@ import ( consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" consensusTx "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" consensusResults "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction/results" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" roothash "github.com/oasisprotocol/oasis-core/go/roothash/api" "github.com/oasisprotocol/oasis-core/go/roothash/api/block" "github.com/oasisprotocol/oasis-core/go/roothash/api/commitment" @@ -421,7 +421,7 @@ type RuntimeExecuteTxBatchResponse struct { // RuntimeKeyManagerStatusUpdateRequest is a runtime key manager status update request message body. type RuntimeKeyManagerStatusUpdateRequest struct { - Status keymanager.Status `json:"status"` + Status secrets.Status `json:"status"` } // RuntimeKeyManagerPolicyUpdateRequest is a runtime key manager policy update request message body. diff --git a/go/runtime/host/tests/tester.go b/go/runtime/host/tests/tester.go index fbdbcab7295..08778a9557a 100644 --- a/go/runtime/host/tests/tester.go +++ b/go/runtime/host/tests/tester.go @@ -15,6 +15,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/logging" "github.com/oasisprotocol/oasis-core/go/common/sgx" keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" "github.com/oasisprotocol/oasis-core/go/runtime/host" "github.com/oasisprotocol/oasis-core/go/runtime/host/protocol" ) @@ -80,15 +81,15 @@ func TestProvisioner( func mockKeyManagerPolicyRequest() (*protocol.Body, error) { // Generate a dummy key manager policy for tests. - policy := keymanager.PolicySGX{ + policy := secrets.PolicySGX{ Serial: 1, - Enclaves: map[sgx.EnclaveIdentity]*keymanager.EnclavePolicySGX{}, + Enclaves: map[sgx.EnclaveIdentity]*secrets.EnclavePolicySGX{}, } - sigPolicy := keymanager.SignedPolicySGX{ + sigPolicy := secrets.SignedPolicySGX{ Policy: policy, } for _, signer := range keymanager.TestSigners[1:] { - sig, err := signature.Sign(signer, keymanager.PolicySGXSignatureContext, cbor.Marshal(policy)) + sig, err := signature.Sign(signer, secrets.PolicySGXSignatureContext, cbor.Marshal(policy)) if err != nil { return nil, fmt.Errorf("failed to sign mock policy: %w", err) } diff --git a/go/runtime/registry/host.go b/go/runtime/registry/host.go index d80b55e5672..818a6cb8128 100644 --- a/go/runtime/registry/host.go +++ b/go/runtime/registry/host.go @@ -21,7 +21,7 @@ import ( consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" consensusResults "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction/results" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registry "github.com/oasisprotocol/oasis-core/go/registry/api" "github.com/oasisprotocol/oasis-core/go/runtime/host" "github.com/oasisprotocol/oasis-core/go/runtime/host/multi" @@ -619,7 +619,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID * n.logger.Debug("watching key manager policy updates", "keymanager", kmRtID) // Subscribe to key manager status updates (policy might change). - stCh, stSub := n.consensus.KeyManager().WatchStatuses() + stCh, stSub := n.consensus.KeyManager().Secrets().WatchStatuses() defer stSub.Close() // Subscribe to epoch transitions (quote policy might change). @@ -646,7 +646,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID * ) var ( - st *keymanager.Status + st *secrets.Status sc *node.SGXConstraints vi *registry.VersionInfo ri *protocol.RuntimeInfoResponse @@ -753,7 +753,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID * } } -func (n *runtimeHostNotifier) updateKeyManagerStatus(ctx context.Context, status *keymanager.Status) error { +func (n *runtimeHostNotifier) updateKeyManagerStatus(ctx context.Context, status *secrets.Status) error { n.logger.Debug("got key manager status update", "status", status) req := &protocol.Body{RuntimeKeyManagerStatusUpdateRequest: &protocol.RuntimeKeyManagerStatusUpdateRequest{ @@ -774,7 +774,7 @@ func (n *runtimeHostNotifier) updateKeyManagerStatus(ctx context.Context, status return nil } -func (n *runtimeHostNotifier) updateKeyManagerPolicy(ctx context.Context, policy *keymanager.SignedPolicySGX) error { +func (n *runtimeHostNotifier) updateKeyManagerPolicy(ctx context.Context, policy *secrets.SignedPolicySGX) error { n.logger.Debug("got key manager policy update", "policy", policy) raw := cbor.Marshal(policy) diff --git a/go/worker/common/committee/keymanager.go b/go/worker/common/committee/keymanager.go index a5ba5be1bbb..7e552fe197d 100644 --- a/go/worker/common/committee/keymanager.go +++ b/go/worker/common/committee/keymanager.go @@ -12,7 +12,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/logging" cmSync "github.com/oasisprotocol/oasis-core/go/common/sync" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" - keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" p2p "github.com/oasisprotocol/oasis-core/go/p2p/api" "github.com/oasisprotocol/oasis-core/go/p2p/rpc" registry "github.com/oasisprotocol/oasis-core/go/registry/api" @@ -225,11 +225,11 @@ func (nt *nodeTracker) Nodes(nodes []signature.PublicKey) map[core.PeerID]signat } func (nt *nodeTracker) trackKeymanagerNodes(ctx context.Context) { - stCh, stSub := nt.consensus.KeyManager().WatchStatuses() + stCh, stSub := nt.consensus.KeyManager().Secrets().WatchStatuses() defer stSub.Close() for { - var status *keymanager.Status + var status *secrets.Status select { case <-ctx.Done(): return diff --git a/go/worker/keymanager/api/api.go b/go/worker/keymanager/api/api.go index 3b17a21b77b..c10f069b6a6 100644 --- a/go/worker/keymanager/api/api.go +++ b/go/worker/keymanager/api/api.go @@ -9,7 +9,7 @@ import ( beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/version" - "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" enclaverpc "github.com/oasisprotocol/oasis-core/go/runtime/enclaverpc/api" ) @@ -109,7 +109,7 @@ type Status struct { // SecretsStatus is the key manager master and ephemeral secrets status. type SecretsStatus struct { // Status is the global key manager committee status. - Status *api.Status `json:"status"` + Status *secrets.Status `json:"status"` // Worker is the key manager master and ephemeral secrets worker status. Worker SecretsWorkerStatus `json:"worker"` @@ -125,7 +125,7 @@ type SecretsWorkerStatus struct { LastRegistration time.Time `json:"last_registration"` // Policy is the master and ephemeral secrets access control policy. - Policy *api.SignedPolicySGX `json:"policy"` + Policy *secrets.SignedPolicySGX `json:"policy"` // PolicyChecksum is the checksum of the policy. PolicyChecksum []byte `json:"policy_checksum"` diff --git a/go/worker/keymanager/config/config.go b/go/worker/keymanager/config/config.go index 6ad60c16495..76e1e368b3c 100644 --- a/go/worker/keymanager/config/config.go +++ b/go/worker/keymanager/config/config.go @@ -7,6 +7,14 @@ type Config struct { RuntimeID string `yaml:"runtime_id"` // Base64-encoded public keys of unadvertised peers that may call protected methods. PrivatePeerPubKeys []string `yaml:"private_peer_pub_keys"` + + // Churp is configuration for Churp application. + Churp []ChurpConfig `yaml:"churp,omitempty"` +} + +type ChurpConfig struct { + // ID is the Churp ID. + ID uint8 `yaml:"id,omitempty"` } // Validate validates the configuration settings. diff --git a/go/worker/keymanager/secrets.go b/go/worker/keymanager/secrets.go index 66e469033fd..c150036f580 100644 --- a/go/worker/keymanager/secrets.go +++ b/go/worker/keymanager/secrets.go @@ -26,6 +26,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/config" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" "github.com/oasisprotocol/oasis-core/go/keymanager/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" p2pAPI "github.com/oasisprotocol/oasis-core/go/p2p/api" registry "github.com/oasisprotocol/oasis-core/go/registry/api" enclaverpc "github.com/oasisprotocol/oasis-core/go/runtime/enclaverpc/api" @@ -43,15 +44,15 @@ const ( ) var insecureRPCMethods = map[string]struct{}{ - api.RPCMethodGetPublicKey: {}, - api.RPCMethodGetPublicEphemeralKey: {}, + secrets.RPCMethodGetPublicKey: {}, + secrets.RPCMethodGetPublicEphemeralKey: {}, } var secureRPCMethods = map[string]struct{}{ - api.RPCMethodGetOrCreateKeys: {}, - api.RPCMethodGetOrCreateEphemeralKeys: {}, - api.RPCMethodReplicateMasterSecret: {}, - api.RPCMethodReplicateEphemeralSecret: {}, + secrets.RPCMethodGetOrCreateKeys: {}, + secrets.RPCMethodGetOrCreateEphemeralKeys: {}, + secrets.RPCMethodReplicateMasterSecret: {}, + secrets.RPCMethodReplicateEphemeralSecret: {}, } // Ensure the secrets worker implements the RPCAccessController interface. @@ -75,15 +76,15 @@ type secretsWorker struct { backend api.Backend status workerKm.SecretsStatus // Guarded by mutex. - kmStatus *api.Status + kmStatus *secrets.Status initEnclaveInProgress bool initEnclaveRequired bool - initEnclaveDoneCh chan *api.SignedInitResponse + initEnclaveDoneCh chan *secrets.SignedInitResponse initEnclaveRetryCh <-chan time.Time initEnclaveRetryTicker *backoff.Ticker - mstSecret *api.SignedEncryptedMasterSecret + mstSecret *secrets.SignedEncryptedMasterSecret loadMstSecRetry int genMstSecDoneCh chan bool @@ -91,7 +92,7 @@ type secretsWorker struct { genMstSecInProgress bool genMstSecRetry int - ephSecret *api.SignedEncryptedEphemeralSecret + ephSecret *secrets.SignedEncryptedEphemeralSecret loadEphSecRetry int genEphSecDoneCh chan bool @@ -147,7 +148,7 @@ func newSecretsWorker( kmWorker: kmWorker, commonWorker: commonWorker, backend: backend, - initEnclaveDoneCh: make(chan *api.SignedInitResponse, 1), + initEnclaveDoneCh: make(chan *secrets.SignedInitResponse, 1), genMstSecDoneCh: make(chan bool, 1), genMstSecEpoch: math.MaxUint64, genEphSecDoneCh: make(chan bool, 1), @@ -226,16 +227,16 @@ func (w *secretsWorker) Authorize(method string, kind enclaverpc.Kind, peerID co // Other peers must undergo the authorization process. switch method { - case api.RPCMethodGetOrCreateKeys, api.RPCMethodGetOrCreateEphemeralKeys: + case secrets.RPCMethodGetOrCreateKeys, secrets.RPCMethodGetOrCreateEphemeralKeys: return w.authorizeNode(peerID, kmStatus) - case api.RPCMethodReplicateMasterSecret, api.RPCMethodReplicateEphemeralSecret: + case secrets.RPCMethodReplicateMasterSecret, secrets.RPCMethodReplicateEphemeralSecret: return w.authorizeKeyManager(peerID) default: return fmt.Errorf("unsupported method: %s", method) } } -func (w *secretsWorker) authorizeNode(peerID core.PeerID, kmStatus *api.Status) error { +func (w *secretsWorker) authorizeNode(peerID core.PeerID, kmStatus *secrets.Status) error { capabilityTEE, err := w.kmWorker.GetHostedRuntimeCapabilityTEE() if err != nil { return err @@ -306,15 +307,15 @@ func (w *secretsWorker) work(ctx context.Context, hrt host.RichRuntime) { defer hrtSub.Close() // Subscribe to key manager status updates. - statusCh, statusSub := w.backend.WatchStatuses() + statusCh, statusSub := w.backend.Secrets().WatchStatuses() defer statusSub.Close() // Subscribe to key manager master secret publications. - mstCh, mstSub := w.backend.WatchMasterSecrets() + mstCh, mstSub := w.backend.Secrets().WatchMasterSecrets() defer mstSub.Close() // Subscribe to key manager ephemeral secret publications. - ephCh, ephSub := w.backend.WatchEphemeralSecrets() + ephCh, ephSub := w.backend.Secrets().WatchEphemeralSecrets() defer ephSub.Close() // Subscribe to epoch transitions in order to know when we need to choose @@ -447,7 +448,7 @@ func (w *secretsWorker) handleRuntimeHostEvent(ev *host.Event) { } } -func (w *secretsWorker) handleStatusUpdate(kmStatus *api.Status) { +func (w *secretsWorker) handleStatusUpdate(kmStatus *secrets.Status) { if kmStatus == nil || !kmStatus.ID.Equal(&w.runtimeID) { return } @@ -495,7 +496,7 @@ func (w *secretsWorker) handleInitEnclave() { // Enclave initialization can take a long time (e.g. when master secrets // need to be replicated), so don't block the loop. - initEnclave := func(kmStatus *api.Status) { + initEnclave := func(kmStatus *secrets.Status) { rsp, err := w.initEnclave(kmStatus) if err != nil { w.logger.Error("failed to initialize enclave", @@ -508,15 +509,15 @@ func (w *secretsWorker) handleInitEnclave() { go initEnclave(w.kmStatus) } -func (w *secretsWorker) initEnclave(kmStatus *api.Status) (*api.SignedInitResponse, error) { +func (w *secretsWorker) initEnclave(kmStatus *secrets.Status) (*secrets.SignedInitResponse, error) { w.logger.Info("initializing key manager enclave") // Initialize the key manager. - args := api.InitRequest{ + args := secrets.InitRequest{ Status: *kmStatus, } - var rsp api.SignedInitResponse - if err := w.kmWorker.callEnclaveLocal(api.RPCMethodInit, args, &rsp); err != nil { + var rsp secrets.SignedInitResponse + if err := w.kmWorker.callEnclaveLocal(secrets.RPCMethodInit, args, &rsp); err != nil { w.logger.Error("failed to initialize enclave", "err", err, ) @@ -561,7 +562,7 @@ func (w *secretsWorker) initEnclave(kmStatus *api.Status) (*api.SignedInitRespon return &rsp, nil } -func (w *secretsWorker) handleInitEnclaveDone(rsp *api.SignedInitResponse) { +func (w *secretsWorker) handleInitEnclaveDone(rsp *secrets.SignedInitResponse) { // Discard the response if the runtime is not ready and retry later. version, err := w.kmWorker.GetHostedRuntimeActiveVersion() if err != nil { @@ -596,7 +597,7 @@ func (w *secretsWorker) handleInitEnclaveDone(rsp *api.SignedInitResponse) { } } -func (w *secretsWorker) registerNode(rsp *api.SignedInitResponse, version version.Version) { +func (w *secretsWorker) registerNode(rsp *secrets.SignedInitResponse, version version.Version) { w.logger.Info("registering key manager", "is_secure", rsp.InitResponse.IsSecure, "checksum", hex.EncodeToString(rsp.InitResponse.Checksum), @@ -665,7 +666,7 @@ func (w *secretsWorker) updateGenerateMasterSecretEpoch() { ) } -func (w *secretsWorker) handleNewMasterSecret(secret *api.SignedEncryptedMasterSecret) { +func (w *secretsWorker) handleNewMasterSecret(secret *secrets.SignedEncryptedMasterSecret) { if !secret.Secret.ID.Equal(&w.runtimeID) { return } @@ -715,18 +716,18 @@ func (w *secretsWorker) handleLoadMasterSecret() { w.handleInitEnclave() } -func (w *secretsWorker) loadMasterSecret(sigSecret *api.SignedEncryptedMasterSecret) error { +func (w *secretsWorker) loadMasterSecret(sigSecret *secrets.SignedEncryptedMasterSecret) error { w.logger.Info("loading master secret", "generation", sigSecret.Secret.Generation, "epoch", sigSecret.Secret.Epoch, ) - args := api.LoadMasterSecretRequest{ + args := secrets.LoadMasterSecretRequest{ SignedSecret: *sigSecret, } var rsp protocol.Empty - if err := w.kmWorker.callEnclaveLocal(api.RPCMethodLoadMasterSecret, args, &rsp); err != nil { + if err := w.kmWorker.callEnclaveLocal(secrets.RPCMethodLoadMasterSecret, args, &rsp); err != nil { w.logger.Error("failed to load master secret", "err", err, ) @@ -773,7 +774,7 @@ func (w *secretsWorker) handleGenerateMasterSecret(ctx context.Context, height i w.genMstSecRetry++ // Submitting transaction can take time, so don't block the loop. - generateMasterSecret := func(kmStatus *api.Status) { + generateMasterSecret := func(kmStatus *secrets.Status) { if err := w.generateMasterSecret(ctx, w.runtimeID, height, nextGen, nextEpoch, kmStatus); err != nil { w.logger.Error("failed to generate master secret", "err", err, @@ -788,7 +789,7 @@ func (w *secretsWorker) handleGenerateMasterSecret(ctx context.Context, height i go generateMasterSecret(w.kmStatus) } -func (w *secretsWorker) generateMasterSecret(ctx context.Context, runtimeID common.Namespace, height int64, generation uint64, epoch beacon.EpochTime, kmStatus *api.Status) error { +func (w *secretsWorker) generateMasterSecret(ctx context.Context, runtimeID common.Namespace, height int64, generation uint64, epoch beacon.EpochTime, kmStatus *secrets.Status) error { w.logger.Info("generating master secret", "height", height, "generation", generation, @@ -796,11 +797,11 @@ func (w *secretsWorker) generateMasterSecret(ctx context.Context, runtimeID comm ) // Check if the master secret has been proposed in this epoch. // Note that despite this check, the nodes can still publish master secrets at the same time. - lastSecret, err := w.commonWorker.Consensus.KeyManager().GetMasterSecret(ctx, ®istry.NamespaceQuery{ + lastSecret, err := w.commonWorker.Consensus.KeyManager().Secrets().GetMasterSecret(ctx, ®istry.NamespaceQuery{ Height: consensus.HeightLatest, ID: runtimeID, }) - if err != nil && err != api.ErrNoSuchMasterSecret { + if err != nil && err != secrets.ErrNoSuchMasterSecret { return err } if lastSecret != nil && epoch == lastSecret.Secret.Epoch { @@ -820,13 +821,13 @@ func (w *secretsWorker) generateMasterSecret(ctx context.Context, runtimeID comm } // Generate master secret. - args := api.GenerateMasterSecretRequest{ + args := secrets.GenerateMasterSecretRequest{ Generation: generation, Epoch: epoch, } - var rsp api.GenerateMasterSecretResponse - if err = w.kmWorker.callEnclaveLocal(api.RPCMethodGenerateMasterSecret, args, &rsp); err != nil { + var rsp secrets.GenerateMasterSecretResponse + if err = w.kmWorker.callEnclaveLocal(secrets.RPCMethodGenerateMasterSecret, args, &rsp); err != nil { w.logger.Error("failed to generate master secret", "err", err, ) @@ -849,7 +850,7 @@ func (w *secretsWorker) generateMasterSecret(ctx context.Context, runtimeID comm } // Publish transaction. - tx := api.NewPublishMasterSecretTx(0, nil, &rsp.SignedSecret) + tx := secrets.NewPublishMasterSecretTx(0, nil, &rsp.SignedSecret) if err = consensus.SignAndSubmitTx(ctx, w.commonWorker.Consensus, w.commonWorker.Identity.NodeSigner, tx); err != nil { return err } @@ -877,7 +878,7 @@ func (w *secretsWorker) handleGenerateMasterSecretDone(ok bool) { } } -func (w *secretsWorker) handleNewEphemeralSecret(secret *api.SignedEncryptedEphemeralSecret, epoch beacon.EpochTime) { +func (w *secretsWorker) handleNewEphemeralSecret(secret *secrets.SignedEncryptedEphemeralSecret, epoch beacon.EpochTime) { if !secret.Secret.ID.Equal(&w.runtimeID) { return } @@ -923,17 +924,17 @@ func (w *secretsWorker) handleLoadEphemeralSecret() { w.loadEphSecRetry = math.MaxInt64 } -func (w *secretsWorker) loadEphemeralSecret(sigSecret *api.SignedEncryptedEphemeralSecret) error { +func (w *secretsWorker) loadEphemeralSecret(sigSecret *secrets.SignedEncryptedEphemeralSecret) error { w.logger.Info("loading ephemeral secret", "epoch", sigSecret.Secret.Epoch, ) - args := api.LoadEphemeralSecretRequest{ + args := secrets.LoadEphemeralSecretRequest{ SignedSecret: *sigSecret, } var rsp protocol.Empty - if err := w.kmWorker.callEnclaveLocal(api.RPCMethodLoadEphemeralSecret, args, &rsp); err != nil { + if err := w.kmWorker.callEnclaveLocal(secrets.RPCMethodLoadEphemeralSecret, args, &rsp); err != nil { w.logger.Error("failed to load ephemeral secret", "err", err, ) @@ -978,7 +979,7 @@ func (w *secretsWorker) handleGenerateEphemeralSecret(ctx context.Context, heigh w.genEphSecRetry++ // Submitting transaction can take time, so don't block the loop. - generateEphemeralSecret := func(kmStatus *api.Status) { + generateEphemeralSecret := func(kmStatus *secrets.Status) { if err := w.generateEphemeralSecret(ctx, w.runtimeID, height, nextEpoch, kmStatus); err != nil { w.logger.Error("failed to generate ephemeral secret", "err", err, @@ -993,7 +994,7 @@ func (w *secretsWorker) handleGenerateEphemeralSecret(ctx context.Context, heigh go generateEphemeralSecret(w.kmStatus) } -func (w *secretsWorker) generateEphemeralSecret(ctx context.Context, runtimeID common.Namespace, height int64, epoch beacon.EpochTime, kmStatus *api.Status) error { +func (w *secretsWorker) generateEphemeralSecret(ctx context.Context, runtimeID common.Namespace, height int64, epoch beacon.EpochTime, kmStatus *secrets.Status) error { w.logger.Info("generating ephemeral secret", "height", height, "epoch", epoch, @@ -1001,11 +1002,11 @@ func (w *secretsWorker) generateEphemeralSecret(ctx context.Context, runtimeID c // Check if the ephemeral secret has been published in this epoch. // Note that despite this check, the nodes can still publish ephemeral secrets at the same time. - lastSecret, err := w.commonWorker.Consensus.KeyManager().GetEphemeralSecret(ctx, ®istry.NamespaceQuery{ + lastSecret, err := w.commonWorker.Consensus.KeyManager().Secrets().GetEphemeralSecret(ctx, ®istry.NamespaceQuery{ Height: consensus.HeightLatest, ID: runtimeID, }) - if err != nil && err != api.ErrNoSuchEphemeralSecret { + if err != nil && err != secrets.ErrNoSuchEphemeralSecret { return err } if lastSecret != nil && epoch == lastSecret.Secret.Epoch { @@ -1020,12 +1021,12 @@ func (w *secretsWorker) generateEphemeralSecret(ctx context.Context, runtimeID c } // Generate ephemeral secret. - args := api.GenerateEphemeralSecretRequest{ + args := secrets.GenerateEphemeralSecretRequest{ Epoch: epoch, } - var rsp api.GenerateEphemeralSecretResponse - if err = w.kmWorker.callEnclaveLocal(api.RPCMethodGenerateEphemeralSecret, args, &rsp); err != nil { + var rsp secrets.GenerateEphemeralSecretResponse + if err = w.kmWorker.callEnclaveLocal(secrets.RPCMethodGenerateEphemeralSecret, args, &rsp); err != nil { w.logger.Error("failed to generate ephemeral secret", "err", err, ) @@ -1048,7 +1049,7 @@ func (w *secretsWorker) generateEphemeralSecret(ctx context.Context, runtimeID c } // Publish transaction. - tx := api.NewPublishEphemeralSecretTx(0, nil, &rsp.SignedSecret) + tx := secrets.NewPublishEphemeralSecretTx(0, nil, &rsp.SignedSecret) if err = consensus.SignAndSubmitTx(ctx, w.commonWorker.Consensus, w.commonWorker.Identity.NodeSigner, tx); err != nil { return err } diff --git a/runtime/src/consensus/state/keymanager.rs b/runtime/src/consensus/state/keymanager.rs index 72d27b80348..bc33d7e8abb 100644 --- a/runtime/src/consensus/state/keymanager.rs +++ b/runtime/src/consensus/state/keymanager.rs @@ -154,8 +154,8 @@ mod test { }; #[test] - fn test_keymanager_state_interop() { - // Keep in sync with go/consensus/cometbft/apps/keymanager/state/interop/interop.go. + fn test_keymanager_secrets_state_interop() { + // Keep in sync with go/consensus/cometbft/apps/keymanager/secrets/state/interop/interop.go. // If mock consensus state changes, update the root hash bellow. // See protocol server stdout for hash. // To make the hash show up during tests, run "cargo test" as