ceremonyclient/client/cmd/root.go
2025-04-07 10:12:48 -08:00

205 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/cloudflare/circl/sign/ed448"
"github.com/spf13/cobra"
"golang.org/x/crypto/sha3"
clientConfig "source.quilibrium.com/quilibrium/monorepo/client/cmd/config"
"source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
token "source.quilibrium.com/quilibrium/monorepo/client/cmd/token"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
var configDirectory string
var signatureCheck bool = true
var NodeConfig *config.Config
var simulateFail bool
var DryRun bool = false
var ClientConfig *utils.ClientConfig
var StandardizedQClientFileName string = "qclient-" + config.GetVersionString() + "-" + osType + "-" + arch
var rootCmd = &cobra.Command{
Use: "qclient",
Short: "Quilibrium client",
Long: `Quilibrium client is a command-line tool for managing Quilibrium nodes.
It provides commands for installing, updating, and managing Quilibrium nodes.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if cmd.Name() == "help" || cmd.Name() == "download-signatures" {
return
}
if !utils.FileExists(utils.ClientConfigPath) {
fmt.Println("Client config not found, creating default config")
utils.CreateDefaultConfig()
}
clientConfig, err := utils.LoadClientConfig()
if err != nil {
fmt.Printf("Error loading client config: %v\n", err)
os.Exit(1)
}
if !clientConfig.SignatureCheck {
signatureCheck = false
}
if signatureCheck {
ex, err := os.Executable()
if err != nil {
panic(err)
}
b, err := os.ReadFile(ex)
if err != nil {
fmt.Println(
"Error encountered during signature check are you running this " +
"from source? (use --signature-check=false)",
)
panic(err)
}
checksum := sha3.Sum256(b)
// First check var data path for signatures
varDataPath := filepath.Join(utils.ClientDataPath, config.GetVersionString())
digestPath := filepath.Join(varDataPath, StandardizedQClientFileName+".dgst")
fmt.Printf("Checking signature for %s\n", digestPath)
// Try to read digest from var data path first
digest, err := os.ReadFile(digestPath)
if err != nil {
// Fall back to checking next to executable
digest, err = os.ReadFile(ex + ".dgst")
if err != nil {
fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
fmt.Println("The signature files (if they exist)can be downloaded with the 'qclient download-signatures' command")
fmt.Println("You can also use --signature-check=false in your command to skip this prompt")
reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n')
response = strings.TrimSpace(strings.ToLower(response))
if response != "y" && response != "yes" {
fmt.Println("Exiting due to missing digest file")
os.Exit(1)
}
fmt.Println("Continuing without signature verification")
signatureCheck = false
}
}
if signatureCheck {
parts := strings.Split(string(digest), " ")
if len(parts) != 2 {
fmt.Println("Invalid digest file format")
os.Exit(1)
}
digestBytes, err := hex.DecodeString(parts[1][:64])
if err != nil {
fmt.Println("Invalid digest file format")
os.Exit(1)
}
if !bytes.Equal(checksum[:], digestBytes) {
fmt.Println("Invalid digest for node")
os.Exit(1)
}
count := 0
for i := 1; i <= len(config.Signatories); i++ {
// Try var data path first for signature files
signatureFile := filepath.Join(varDataPath, fmt.Sprintf("%s.dgst.sig.%d", filepath.Base(ex), i))
sig, err := os.ReadFile(signatureFile)
if err != nil {
// Fall back to checking next to executable
signatureFile = fmt.Sprintf(ex+".dgst.sig.%d", i)
sig, err = os.ReadFile(signatureFile)
if err != nil {
continue
}
}
pubkey, _ := hex.DecodeString(config.Signatories[i-1])
if !ed448.Verify(pubkey, digest, sig, "") {
fmt.Printf("Failed signature check for signatory #%d\n", i)
os.Exit(1)
}
count++
}
if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
fmt.Printf("Quorum on signatures not met")
os.Exit(1)
}
fmt.Println("Signature check passed")
}
} else {
fmt.Println("Signature check bypassed, be sure you know what you're doing")
fmt.Println("----------------------------------------------------------")
fmt.Println("")
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func signatureCheckDefault() bool {
envVarValue, envVarExists := os.LookupEnv("QUILIBRIUM_SIGNATURE_CHECK")
if envVarExists {
def, err := strconv.ParseBool(envVarValue)
if err == nil {
return def
} else {
fmt.Println("Invalid environment variable QUILIBRIUM_SIGNATURE_CHECK, must be 'true' or 'false'. Got: " + envVarValue)
}
}
return true
}
func init() {
rootCmd.PersistentFlags().BoolVar(
&DryRun,
"dry-run",
false,
"runs the command (if applicable) without actually mutating state (printing effect output)",
)
rootCmd.PersistentFlags().BoolVar(
&signatureCheck,
"signature-check",
signatureCheckDefault(),
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
)
// Add the node command
rootCmd.AddCommand(node.NodeCmd)
rootCmd.AddCommand(clientConfig.ConfigCmd)
rootCmd.AddCommand(token.TokenCmd)
}