From 06d8f2d268ae93c3475622e2990ac6aff311c585 Mon Sep 17 00:00:00 2001 From: bruce-riley <96066700+bruce-riley@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:06:12 -0500 Subject: [PATCH] Node: Keygen changes (#3401) --- node/cmd/guardiand/adminclient.go | 8 ++- node/cmd/guardiand/keygen.go | 44 +++++++++++++ node/cmd/guardiand/node.go | 9 +-- node/hack/accountant/send_obs.go | 50 +------------- .../common/armoredKey.go} | 65 ++++--------------- node/pkg/devnet/guardiankey.go | 28 ++++++++ 6 files changed, 93 insertions(+), 111 deletions(-) create mode 100644 node/cmd/guardiand/keygen.go rename node/{cmd/guardiand/guardiankey.go => pkg/common/armoredKey.go} (55%) create mode 100644 node/pkg/devnet/guardiankey.go diff --git a/node/cmd/guardiand/adminclient.go b/node/cmd/guardiand/adminclient.go index ba94f2185..db4993ae9 100644 --- a/node/cmd/guardiand/adminclient.go +++ b/node/cmd/guardiand/adminclient.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/pflag" "golang.org/x/crypto/sha3" + "github.com/certusone/wormhole/node/pkg/common" gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1" publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1" "github.com/wormhole-foundation/wormhole/sdk" @@ -39,6 +40,7 @@ import ( var ( clientSocketPath *string shouldBackfill *bool + unsafeDevnetMode *bool ) func init() { @@ -68,6 +70,10 @@ func init() { SignExistingVaaCmd.Flags().AddFlagSet(pf) SignExistingVaasFromCSVCmd.Flags().AddFlagSet(pf) + adminClientSignWormchainAddressFlags := pflag.NewFlagSet("adminClientSignWormchainAddressFlags", pflag.ContinueOnError) + unsafeDevnetMode = adminClientSignWormchainAddressFlags.Bool("unsafeDevMode", false, "Run in unsafe devnet mode") + AdminClientSignWormchainAddress.Flags().AddFlagSet(adminClientSignWormchainAddressFlags) + AdminCmd.AddCommand(AdminClientInjectGuardianSetUpdateCmd) AdminCmd.AddCommand(AdminClientFindMissingMessagesCmd) AdminCmd.AddCommand(AdminClientGovernanceVAAVerifyCmd) @@ -225,7 +231,7 @@ func runSignWormchainValidatorAddress(cmd *cobra.Command, args []string) error { if !strings.HasPrefix(wormchainAddress, "wormhole") || strings.HasPrefix(wormchainAddress, "wormholeval") { return fmt.Errorf("must provide a bech32 address that has 'wormhole' prefix") } - gk, err := loadGuardianKey(guardianKeyPath) + gk, err := common.LoadGuardianKey(guardianKeyPath, *unsafeDevnetMode) if err != nil { return fmt.Errorf("failed to load guardian key: %w", err) } diff --git a/node/cmd/guardiand/keygen.go b/node/cmd/guardiand/keygen.go new file mode 100644 index 000000000..8596fd9ea --- /dev/null +++ b/node/cmd/guardiand/keygen.go @@ -0,0 +1,44 @@ +package guardiand + +import ( + "crypto/ecdsa" + "crypto/rand" + "log" + + "github.com/certusone/wormhole/node/pkg/common" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/spf13/cobra" +) + +var keyDescription *string +var blockType *string + +func init() { + keyDescription = KeygenCmd.Flags().String("desc", "", "Human-readable key description (optional)") + blockType = KeygenCmd.Flags().String("block-type", common.GuardianKeyArmoredBlock, "block type of armored file (optional)") +} + +var KeygenCmd = &cobra.Command{ + Use: "keygen [KEYFILE]", + Short: "Create guardian key at the specified path", + Run: runKeygen, + Args: cobra.ExactArgs(1), +} + +func runKeygen(cmd *cobra.Command, args []string) { + common.LockMemory() + common.SetRestrictiveUmask() + + log.Print("Creating new key at ", args[0]) + + gk, err := ecdsa.GenerateKey(ethcrypto.S256(), rand.Reader) + if err != nil { + log.Fatalf("failed to generate key: %v", err) + } + + err = common.WriteArmoredKey(gk, *keyDescription, args[0], *blockType, false) + if err != nil { + log.Fatalf("failed to write key: %v", err) + } +} diff --git a/node/cmd/guardiand/node.go b/node/cmd/guardiand/node.go index 42f6a8fbe..e82b42ae6 100644 --- a/node/cmd/guardiand/node.go +++ b/node/cmd/guardiand/node.go @@ -781,15 +781,10 @@ func runNode(cmd *cobra.Command, args []string) { // In devnet mode, we generate a deterministic guardian key and write it to disk. if *unsafeDevMode { - gk, err := generateDevnetGuardianKey() + err := devnet.GenerateAndStoreDevnetGuardianKey(*guardianKeyPath) if err != nil { logger.Fatal("failed to generate devnet guardian key", zap.Error(err)) } - - err = writeGuardianKey(gk, "auto-generated deterministic devnet key", *guardianKeyPath, true) - if err != nil { - logger.Fatal("failed to write devnet guardian key", zap.Error(err)) - } } // Database @@ -797,7 +792,7 @@ func runNode(cmd *cobra.Command, args []string) { defer db.Close() // Guardian key - gk, err := loadGuardianKey(*guardianKeyPath) + gk, err := common.LoadGuardianKey(*guardianKeyPath, *unsafeDevMode) if err != nil { logger.Fatal("failed to load guardian key", zap.Error(err)) } diff --git a/node/hack/accountant/send_obs.go b/node/hack/accountant/send_obs.go index d414c8889..d3166d840 100644 --- a/node/hack/accountant/send_obs.go +++ b/node/hack/accountant/send_obs.go @@ -7,24 +7,15 @@ import ( "context" "crypto/ecdsa" "encoding/hex" - "fmt" - "io" - "os" "time" "github.com/certusone/wormhole/node/pkg/accountant" "github.com/certusone/wormhole/node/pkg/common" - nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1" "github.com/certusone/wormhole/node/pkg/wormconn" "github.com/wormhole-foundation/wormhole/sdk/vaa" sdktx "github.com/cosmos/cosmos-sdk/types/tx" - ethCrypto "github.com/ethereum/go-ethereum/crypto" - - "golang.org/x/crypto/openpgp/armor" //nolint - "google.golang.org/protobuf/proto" - "go.uber.org/zap" ) @@ -54,7 +45,7 @@ func main() { ) logger.Info("Loading guardian key", zap.String("guardianKeyPath", guardianKeyPath)) - gk, err := loadGuardianKey(guardianKeyPath) + gk, err := common.LoadGuardianKey(guardianKeyPath, true) if err != nil { logger.Fatal("failed to load guardian key", zap.Error(err)) } @@ -448,42 +439,3 @@ func submit( return accountant.SubmitObservationsToContract(ctx, logger, gk, gsIndex, guardianIndex, wormchainConn, contract, msgs) } - -const ( - GuardianKeyArmoredBlock = "WORMHOLE GUARDIAN PRIVATE KEY" -) - -// loadGuardianKey loads a serialized guardian key from disk. -func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) { - f, err := os.Open(filename) - if err != nil { - return nil, fmt.Errorf("failed to open file: %w", err) - } - - p, err := armor.Decode(f) - if err != nil { - return nil, fmt.Errorf("failed to read armored file: %w", err) - } - - if p.Type != GuardianKeyArmoredBlock { - return nil, fmt.Errorf("invalid block type: %s", p.Type) - } - - b, err := io.ReadAll(p.Body) - if err != nil { - return nil, fmt.Errorf("failed to read file: %w", err) - } - - var m nodev1.GuardianKey - err = proto.Unmarshal(b, &m) - if err != nil { - return nil, fmt.Errorf("failed to deserialize protobuf: %w", err) - } - - gk, err := ethCrypto.ToECDSA(m.Data) - if err != nil { - return nil, fmt.Errorf("failed to deserialize raw key data: %w", err) - } - - return gk, nil -} diff --git a/node/cmd/guardiand/guardiankey.go b/node/pkg/common/armoredKey.go similarity index 55% rename from node/cmd/guardiand/guardiankey.go rename to node/pkg/common/armoredKey.go index 5491bfbea..7845213fb 100644 --- a/node/cmd/guardiand/guardiankey.go +++ b/node/pkg/common/armoredKey.go @@ -1,61 +1,30 @@ -package guardiand +package common import ( "crypto/ecdsa" - "crypto/rand" "errors" "fmt" "io" - "log" "os" - "github.com/certusone/wormhole/node/pkg/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/spf13/cobra" "golang.org/x/crypto/openpgp/armor" //nolint "google.golang.org/protobuf/proto" - "github.com/certusone/wormhole/node/pkg/devnet" nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1" ) -var keyDescription *string - const ( GuardianKeyArmoredBlock = "WORMHOLE GUARDIAN PRIVATE KEY" ) -func init() { - keyDescription = KeygenCmd.Flags().String("desc", "", "Human-readable key description (optional)") +// LoadGuardianKey loads a serialized guardian key from disk. +func LoadGuardianKey(filename string, unsafeDevMode bool) (*ecdsa.PrivateKey, error) { + return LoadArmoredKey(filename, GuardianKeyArmoredBlock, unsafeDevMode) } -var KeygenCmd = &cobra.Command{ - Use: "keygen [KEYFILE]", - Short: "Create guardian key at the specified path", - Run: runKeygen, - Args: cobra.ExactArgs(1), -} - -func runKeygen(cmd *cobra.Command, args []string) { - common.LockMemory() - common.SetRestrictiveUmask() - - log.Print("Creating new key at ", args[0]) - - gk, err := ecdsa.GenerateKey(ethcrypto.S256(), rand.Reader) - if err != nil { - log.Fatalf("failed to generate key: %v", err) - } - - err = writeGuardianKey(gk, *keyDescription, args[0], false) - if err != nil { - log.Fatalf("failed to write key: %v", err) - } -} - -// loadGuardianKey loads a serialized guardian key from disk. -func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) { +// LoadArmoredKey loads a serialized key from disk. +func LoadArmoredKey(filename string, blockType string, unsafeDevMode bool) (*ecdsa.PrivateKey, error) { f, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) @@ -66,7 +35,7 @@ func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) { return nil, fmt.Errorf("failed to read armored file: %w", err) } - if p.Type != GuardianKeyArmoredBlock { + if p.Type != blockType { return nil, fmt.Errorf("invalid block type: %s", p.Type) } @@ -81,7 +50,7 @@ func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) { return nil, fmt.Errorf("failed to deserialize protobuf: %w", err) } - if !*unsafeDevMode && m.UnsafeDeterministicKey { + if !unsafeDevMode && m.UnsafeDeterministicKey { return nil, errors.New("refusing to use deterministic key in production") } @@ -93,8 +62,8 @@ func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) { return gk, nil } -// writeGuardianKey serializes a guardian key and writes it to disk. -func writeGuardianKey(key *ecdsa.PrivateKey, description string, filename string, unsafe bool) error { +// WriteArmoredKey serializes a key and writes it to disk. +func WriteArmoredKey(key *ecdsa.PrivateKey, description string, filename string, blockType string, unsafe bool) error { if _, err := os.Stat(filename); !os.IsNotExist(err) { return errors.New("refusing to override existing key") } @@ -122,7 +91,7 @@ func writeGuardianKey(key *ecdsa.PrivateKey, description string, filename string if description != "" { headers["Description"] = description } - a, err := armor.Encode(f, GuardianKeyArmoredBlock, headers) + a, err := armor.Encode(f, blockType, headers) if err != nil { panic(err) } @@ -136,15 +105,3 @@ func writeGuardianKey(key *ecdsa.PrivateKey, description string, filename string } return f.Close() } - -// generateDevnetGuardianKey returns a deterministic testnet key. -func generateDevnetGuardianKey() (*ecdsa.PrivateKey, error) { - // Figure out our devnet index - idx, err := devnet.GetDevnetIndex() - if err != nil { - return nil, err - } - - // Generate guardian key - return devnet.InsecureDeterministicEcdsaKeyByIndex(ethcrypto.S256(), uint64(idx)), nil -} diff --git a/node/pkg/devnet/guardiankey.go b/node/pkg/devnet/guardiankey.go new file mode 100644 index 000000000..7f54d608e --- /dev/null +++ b/node/pkg/devnet/guardiankey.go @@ -0,0 +1,28 @@ +package devnet + +import ( + "fmt" + + "github.com/certusone/wormhole/node/pkg/common" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" +) + +// GenerateAndStoreDevnetGuardianKey returns a deterministic testnet key. +func GenerateAndStoreDevnetGuardianKey(filename string) error { + // Figure out our devnet index + idx, err := GetDevnetIndex() + if err != nil { + return err + } + + // Generate the guardian key. + gk := InsecureDeterministicEcdsaKeyByIndex(ethcrypto.S256(), uint64(idx)) + + // Store it to disk. + if err := common.WriteArmoredKey(gk, "auto-generated deterministic devnet key", filename, common.GuardianKeyArmoredBlock, true); err != nil { + return fmt.Errorf("failed to store generated guardian key: %w", err) + } + + return nil +}