Skip to content

Commit

Permalink
keymanager/src/client: Fetch public keys using insecure RPC requests
Browse files Browse the repository at this point in the history
  • Loading branch information
peternose committed Jan 3, 2023
1 parent 9648f8c commit 4093b34
Show file tree
Hide file tree
Showing 20 changed files with 630 additions and 148 deletions.
1 change: 1 addition & 0 deletions .changelog/5101.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
keymanager/src/client: Fetch public keys using insecure RPC requests
53 changes: 35 additions & 18 deletions go/consensus/tendermint/apps/keymanager/keymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ func (app *keymanagerApplication) onEpochChange(ctx *tmapi.Context, epoch beacon
"is_initialized", newStatus.IsInitialized,
"is_secure", newStatus.IsSecure,
"checksum", hex.EncodeToString(newStatus.Checksum),
"rsk", newStatus.RSK,
"nodes", newStatus.Nodes,
)

Expand Down Expand Up @@ -260,6 +261,7 @@ func (app *keymanagerApplication) generateStatus(
continue
}

// Skip nodes with mismatched policy.
var nodePolicyHash [api.ChecksumSize]byte
switch len(initResponse.PolicyChecksum) {
case 0:
Expand All @@ -282,24 +284,8 @@ func (app *keymanagerApplication) generateStatus(
continue
}

if status.IsInitialized {
// Already initialized. Check to see if it should be added to
// the node list.
if initResponse.IsSecure != status.IsSecure {
ctx.Logger().Error("Security status mismatch for runtime",
"id", kmrt.ID,
"node_id", n.ID,
)
continue
}
if !bytes.Equal(initResponse.Checksum, status.Checksum) {
ctx.Logger().Error("Checksum mismatch for runtime",
"id", kmrt.ID,
"node_id", n.ID,
)
continue
}
} else {
// Set immutable status fields that cannot change after initialization.
if !status.IsInitialized {
// Not initialized. The first node gets to be the source
// of truth, every other node will sync off it.

Expand All @@ -317,6 +303,37 @@ func (app *keymanagerApplication) generateStatus(
status.Checksum = initResponse.Checksum
}

// Skip nodes with mismatched status fields.
if initResponse.IsSecure != status.IsSecure {
ctx.Logger().Error("Security status mismatch for runtime",
"id", kmrt.ID,
"node_id", n.ID,
)
continue
}
if !bytes.Equal(initResponse.Checksum, status.Checksum) {
ctx.Logger().Error("Checksum mismatch for runtime",
"id", kmrt.ID,
"node_id", n.ID,
)
continue
}

// Update mutable status fields that can change on epoch transitions.
if len(status.Nodes) == 0 {
// The first node gets to be the source of truth for the runtime signing key.
status.RSK = initResponse.RSK
}

// Skip nodes with mismatched runtime signing key.
if initResponse.RSK != status.RSK && (initResponse.RSK == nil || status.RSK == nil || !initResponse.RSK.Equal(*status.RSK)) {
ctx.Logger().Error("Runtime signing key mismatch for runtime",
"id", kmrt.ID,
"node_id", n.ID,
)
continue
}

status.Nodes = append(status.Nodes, n.ID)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func InitializeTestKeyManagerState(ctx context.Context, mkvs mkvs.Tree) error {
Checksum: nil,
Nodes: nil,
Policy: nil,
RSK: nil,
},
{
ID: keymanager2,
Expand All @@ -115,6 +116,7 @@ func InitializeTestKeyManagerState(ctx context.Context, mkvs mkvs.Tree) error {
signers[1].Public(),
},
Policy: &sigPolicy,
RSK: nil,
},
} {
if err := state.SetStatus(ctx, status); err != nil {
Expand Down
10 changes: 7 additions & 3 deletions go/keymanager/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type Status struct {

// 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"`
}

// Backend is a key manager management implementation.
Expand Down Expand Up @@ -100,9 +103,10 @@ func NewUpdatePolicyTx(nonce uint64, fee *transaction.Fee, sigPol *SignedPolicyS
// 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"`
PolicyChecksum []byte `json:"policy_checksum"`
IsSecure bool `json:"is_secure"`
Checksum []byte `json:"checksum"`
PolicyChecksum []byte `json:"policy_checksum"`
RSK *signature.PublicKey `json:"rsk,omitempty"`
}

// SignedInitResponse is the signed initialization RPC response, returned
Expand Down
17 changes: 17 additions & 0 deletions go/oasis-node/cmd/keymanager/keymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
CfgStatusInitialized = "keymanager.status.initialized"
CfgStatusSecure = "keymanager.status.secure"
CfgStatusChecksum = "keymanager.status.checksum"
CfgStatusRSK = "keymanager.status.rsk"

policyFilename = "km_policy.cbor"
statusFilename = "km_status.json"
Expand Down Expand Up @@ -518,12 +519,26 @@ func statusFromFlags() (*kmApi.Status, error) {
return nil, fmt.Errorf("%s is true, but %s is not provided", CfgStatusInitialized, CfgStatusChecksum)
}

var rsk *signature.PublicKey
if viper.GetString(CfgStatusRSK) != "" {
var decodedRsk signature.PublicKey
if err := decodedRsk.UnmarshalText([]byte(viper.GetString(CfgStatusRSK))); err != nil {
return nil, err
}
rsk = &decodedRsk
}

if viper.GetString(CfgStatusRSK) != "" && !viper.GetBool(CfgStatusInitialized) {
return nil, fmt.Errorf("%s provided, but %s is false", CfgStatusRSK, CfgStatusInitialized)
}

return &kmApi.Status{
ID: id,
IsInitialized: viper.GetBool(CfgStatusInitialized),
IsSecure: viper.GetBool(CfgStatusSecure),
Checksum: checksum,
Policy: signedPolicy,
RSK: rsk,
}, nil
}

Expand Down Expand Up @@ -598,6 +613,7 @@ func registerKMInitStatusFlags(cmd *cobra.Command) {
cmd.Flags().Bool(CfgStatusInitialized, false, "is key manager done initializing. Requires "+CfgStatusChecksum)
cmd.Flags().Bool(CfgStatusSecure, false, "is key manager secure")
cmd.Flags().String(CfgStatusChecksum, "", "key manager's master secret verification checksum in hex. Requires "+CfgStatusInitialized)
cmd.Flags().String(CfgStatusRSK, "", "key manager's runtime signing key in base64. Requires "+CfgStatusInitialized)
}

cmd.Flags().AddFlagSet(policyFileFlag)
Expand All @@ -615,6 +631,7 @@ func registerKMInitStatusFlags(cmd *cobra.Command) {
CfgStatusInitialized,
CfgStatusSecure,
CfgStatusChecksum,
CfgStatusRSK,
} {
_ = viper.BindPFlag(v, cmd.Flags().Lookup(v))
}
Expand Down
15 changes: 13 additions & 2 deletions go/runtime/host/protocol/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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"
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"
Expand Down Expand Up @@ -89,6 +90,8 @@ type Body struct {
RuntimeExecuteTxBatchResponse *RuntimeExecuteTxBatchResponse `json:",omitempty"`
RuntimeAbortRequest *Empty `json:",omitempty"`
RuntimeAbortResponse *Empty `json:",omitempty"`
RuntimeKeyManagerStatusUpdateRequest *RuntimeKeyManagerStatusUpdateRequest `json:",omitempty"`
RuntimeKeyManagerStatusUpdateResponse *Empty `json:",omitempty"`
RuntimeKeyManagerPolicyUpdateRequest *RuntimeKeyManagerPolicyUpdateRequest `json:",omitempty"`
RuntimeKeyManagerPolicyUpdateResponse *Empty `json:",omitempty"`
RuntimeKeyManagerQuotePolicyUpdateRequest *RuntimeKeyManagerQuotePolicyUpdateRequest `json:",omitempty"`
Expand Down Expand Up @@ -174,6 +177,9 @@ type Features struct {
// KeyManagerQuotePolicyUpdates is a feature specifying that the runtime supports updating
// key manager's quote policy.
KeyManagerQuotePolicyUpdates bool `json:"key_manager_quote_policy_updates,omitempty"`
// KeyManagerStatusUpdates is a feature specifying that the runtime supports updating
// key manager's status.
KeyManagerStatusUpdates bool `json:"key_manager_status_updates,omitempty"`
}

// HasScheduleControl returns true when the runtime supports the schedule control feature.
Expand Down Expand Up @@ -402,12 +408,17 @@ type RuntimeExecuteTxBatchResponse struct {
Deprecated1 cbor.RawMessage `json:"batch_weight_limits,omitempty"`
}

// RuntimeKeyManagerPolicyUpdateRequest is a runtime key manager policy request message body.
// RuntimeKeyManagerStatusUpdateRequest is a runtime key manager status update request message body.
type RuntimeKeyManagerStatusUpdateRequest struct {
Status keymanager.Status `json:"status"`
}

// RuntimeKeyManagerPolicyUpdateRequest is a runtime key manager policy update request message body.
type RuntimeKeyManagerPolicyUpdateRequest struct {
SignedPolicyRaw []byte `json:"signed_policy_raw"`
}

// RuntimeKeyManagerQuotePolicyUpdateRequest is a runtime key manager quote policy request
// RuntimeKeyManagerQuotePolicyUpdateRequest is a runtime key manager quote policy update request
// message body.
type RuntimeKeyManagerQuotePolicyUpdateRequest struct {
Policy quote.Policy `json:"policy"`
Expand Down
42 changes: 35 additions & 7 deletions go/runtime/registry/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID *
defer retryTicker.Stop()

var (
policyUpdated = true
statusUpdated = true
quotePolicyUpdated = true
runtimeInfoUpdated = false
)
Expand All @@ -571,10 +571,17 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID *
runtimeInfoUpdated = true
}

// Make sure that we actually have a new policy.
if !policyUpdated && st != nil && st.Policy != nil {
if err = n.updateKeyManagerPolicy(ctx, st.Policy); err == nil {
policyUpdated = true
// Make sure that we actually have a new status.
if !statusUpdated && st != nil {
switch {
case ri.Features.KeyManagerStatusUpdates:
if err = n.updateKeyManagerStatus(ctx, st); err == nil {
statusUpdated = true
}
case st.Policy != nil:
if err = n.updateKeyManagerPolicy(ctx, st.Policy); err == nil {
statusUpdated = true
}
}
}

Expand All @@ -596,7 +603,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID *
}
st = newSt

policyUpdated = false
statusUpdated = false
case epoch := <-epoCh:
// Check if the key manager was redeployed, as that is when a new quote policy might
// take effect.
Expand Down Expand Up @@ -640,7 +647,7 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID *
continue
}

policyUpdated = false
statusUpdated = false
quotePolicyUpdated = false
runtimeInfoUpdated = false
case <-retryTicker.C:
Expand All @@ -653,6 +660,27 @@ func (n *runtimeHostNotifier) watchKmPolicyUpdates(ctx context.Context, kmRtID *
}
}

func (n *runtimeHostNotifier) updateKeyManagerStatus(ctx context.Context, status *keymanager.Status) error {
n.logger.Debug("got key manager status update", "status", status)

req := &protocol.Body{RuntimeKeyManagerStatusUpdateRequest: &protocol.RuntimeKeyManagerStatusUpdateRequest{
Status: *status,
}}

ctx, cancel := context.WithTimeout(ctx, notifyTimeout)
defer cancel()

if _, err := n.host.Call(ctx, req); err != nil {
n.logger.Error("failed dispatching key manager status update to runtime",
"err", err,
)
return err
}

n.logger.Debug("key manager status update dispatched")
return nil
}

func (n *runtimeHostNotifier) updateKeyManagerPolicy(ctx context.Context, policy *keymanager.SignedPolicySGX) error {
n.logger.Debug("got key manager policy update", "policy", policy)

Expand Down
4 changes: 4 additions & 0 deletions keymanager/src/api/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub enum KeyManagerError {
PolicyInvalid(#[from] anyhow::Error),
#[error("policy has insufficient signatures")]
PolicyInsufficientSignatures,
#[error("runtime signing key missing")]
RSKMissing,
#[error("signature verification failed: {0}")]
InvalidSignature(anyhow::Error),
#[error(transparent)]
Other(anyhow::Error),
}
9 changes: 7 additions & 2 deletions keymanager/src/api/requests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use oasis_core_runtime::{
common::{crypto::signature::Signature, namespace::Namespace},
common::{
crypto::signature::{PublicKey, Signature},
namespace::Namespace,
},
consensus::beacon::EpochTime,
};

Expand All @@ -17,14 +20,16 @@ pub struct InitRequest {
}

/// Key manager initialization response.
#[derive(Clone, Default, cbor::Encode, cbor::Decode)]
#[derive(Clone, Default, Debug, cbor::Encode, cbor::Decode)]
pub struct InitResponse {
/// True iff the key manager thinks it's running in a secure mode.
pub is_secure: bool,
/// Checksum for validating replication.
pub checksum: Vec<u8>,
/// Checksum for identifying policy.
pub policy_checksum: Vec<u8>,
/// Runtime signing key.
pub rsk: PublicKey,
}

/// Signed InitResponse.
Expand Down
Loading

0 comments on commit 4093b34

Please sign in to comment.