package cli import ( "crypto/rand" "fmt" "math/big" "os" "git.eeqj.de/sneak/secret/internal/secret" "github.com/spf13/cobra" "github.com/tyler-smith/go-bip39" ) func newGenerateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "generate", Short: "Generate random data", Long: `Generate various types of random data including mnemonics and secrets.`, } cmd.AddCommand(newGenerateMnemonicCmd()) cmd.AddCommand(newGenerateSecretCmd()) return cmd } func newGenerateMnemonicCmd() *cobra.Command { return &cobra.Command{ Use: "mnemonic", Short: "Generate a random BIP39 mnemonic phrase", Long: `Generate a cryptographically secure random BIP39 mnemonic phrase that can be used with 'secret init' or 'secret import'.`, RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() return cli.GenerateMnemonic() }, } } func newGenerateSecretCmd() *cobra.Command { cmd := &cobra.Command{ Use: "secret ", Short: "Generate a random secret and store it in the vault", Long: `Generate a cryptographically secure random secret and store it in the current vault under the given name.`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { length, _ := cmd.Flags().GetInt("length") secretType, _ := cmd.Flags().GetString("type") force, _ := cmd.Flags().GetBool("force") cli := NewCLIInstance() return cli.GenerateSecret(args[0], length, secretType, force) }, } cmd.Flags().IntP("length", "l", 16, "Length of the generated secret (default 16)") cmd.Flags().StringP("type", "t", "base58", "Type of secret to generate (base58, alnum)") cmd.Flags().BoolP("force", "f", false, "Overwrite existing secret") return cmd } // GenerateMnemonic generates a random BIP39 mnemonic phrase func (cli *CLIInstance) GenerateMnemonic() error { // Generate 128 bits of entropy for a 12-word mnemonic entropy, err := bip39.NewEntropy(128) if err != nil { return fmt.Errorf("failed to generate entropy: %w", err) } // Create mnemonic from entropy mnemonic, err := bip39.NewMnemonic(entropy) if err != nil { return fmt.Errorf("failed to generate mnemonic: %w", err) } // Output mnemonic to stdout fmt.Println(mnemonic) // Output helpful information to stderr fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "⚠️ IMPORTANT: Save this mnemonic phrase securely!") fmt.Fprintln(os.Stderr, " • Write it down on paper and store it safely") fmt.Fprintln(os.Stderr, " • Do not store it digitally or share it with anyone") fmt.Fprintln(os.Stderr, " • You will need this phrase to recover your secrets") fmt.Fprintln(os.Stderr, " • If you lose this phrase, your secrets cannot be recovered") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Use this mnemonic with:") fmt.Fprintln(os.Stderr, " secret init (to initialize a new secret manager)") fmt.Fprintln(os.Stderr, " secret import (to import into an existing vault)") return nil } // GenerateSecret generates a random secret and stores it in the vault func (cli *CLIInstance) GenerateSecret(secretName string, length int, secretType string, force bool) error { if length < 1 { return fmt.Errorf("length must be at least 1") } var secretValue string var err error switch secretType { case "base58": secretValue, err = generateRandomBase58(length) case "alnum": secretValue, err = generateRandomAlnum(length) case "mnemonic": return fmt.Errorf("mnemonic type not supported for secret generation, use 'secret generate mnemonic' instead") default: return fmt.Errorf("unsupported type: %s (supported: base58, alnum)", secretType) } if err != nil { return fmt.Errorf("failed to generate random secret: %w", err) } // Store the secret in the vault vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir) if err != nil { return err } if err := vault.AddSecret(secretName, []byte(secretValue), force); err != nil { return err } fmt.Printf("Generated and stored %d-character %s secret: %s\n", length, secretType, secretName) return nil } // generateRandomBase58 generates a random base58 string of the specified length func generateRandomBase58(length int) (string, error) { const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" return generateRandomString(length, base58Chars) } // generateRandomAlnum generates a random alphanumeric string of the specified length func generateRandomAlnum(length int) (string, error) { const alnumChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" return generateRandomString(length, alnumChars) } // generateRandomString generates a random string of the specified length using the given character set func generateRandomString(length int, charset string) (string, error) { if length <= 0 { return "", fmt.Errorf("length must be positive") } result := make([]byte, length) charsetLen := big.NewInt(int64(len(charset))) for i := 0; i < length; i++ { randomIndex, err := rand.Int(rand.Reader, charsetLen) if err != nil { return "", fmt.Errorf("failed to generate random number: %w", err) } result[i] = charset[randomIndex.Int64()] } return string(result), nil }