163 lines
5.1 KiB
Go
163 lines
5.1 KiB
Go
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 <name>",
|
|
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
|
|
}
|