Initial commit

This commit is contained in:
2025-07-21 02:29:06 +03:00
parent d4be866383
commit e72949d3f8
27 changed files with 1608 additions and 190 deletions

122
cmd/decrypt.go Normal file
View File

@@ -0,0 +1,122 @@
package cmd
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// tryDecrypt attempts to decrypt using the given key and output/input files.
func tryDecrypt(keyPath, output, input string) error {
ageBin := "age"
if ageBin != "age" {
return fmt.Errorf("invalid binary for decryption: %s", ageBin)
}
ageArgs := []string{"-d", "-i", keyPath, "-o", output, input}
expectedFlags := map[string]bool{"-d": true, "-i": true, "-o": true}
for i, arg := range ageArgs {
if i == 0 || i == 2 || i == 4 {
if !expectedFlags[arg] && i != 0 {
return fmt.Errorf("unexpected flag in age arguments: %s", arg)
}
} else if arg == "" {
return fmt.Errorf("invalid argument for decryption: empty string")
}
}
if !strings.HasSuffix(keyPath, "id_rsa") && !strings.HasSuffix(keyPath, "id_ed25519") {
return fmt.Errorf("invalid key file for decryption: %s", keyPath)
}
if !strings.HasSuffix(output, ".txt") && !strings.HasSuffix(output, ".out") {
return fmt.Errorf("invalid output file for decryption: %s", output)
}
// #nosec G204 -- ageBin and ageArgs are validated above
return exec.Command(ageBin, ageArgs...).Run()
}
// selectSSHKey determines which SSH key to use based on flags and config.
func selectSSHKey(sshKeyFlag string, cfg *Config) string {
if sshKeyFlag != "" {
return sshKeyFlag
}
return cfg.SSHKeyPath
}
// tryAllKeys attempts decryption with all provided keys, returns true on success.
func tryAllKeys(keys []string, input, output string, log *logrus.Logger, triedKeys *[]string) bool {
for _, keyPath := range keys {
*triedKeys = append(*triedKeys, keyPath)
log.WithFields(logrus.Fields{
"input": input,
"output": output,
"sshKey": keyPath,
}).Info("Trying decryption with SSH key")
err := tryDecrypt(keyPath, output, input)
if err == nil {
log.Info("Decryption successful")
return true
}
log.WithError(err).Warnf("Decryption failed with key %s", keyPath)
}
return false
}
// Decrypt returns a cobra.Command that decrypts files using age, scanning local SSH keys if needed.
func Decrypt(cfg *Config, log *logrus.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "decrypt",
Short: "Decrypt a file",
RunE: func(cmd *cobra.Command, _ []string) error {
input, _ := cmd.Flags().GetString("input")
output, _ := cmd.Flags().GetString("output")
sshKeyFlag, _ := cmd.Flags().GetString("ssh-key")
if input == "" {
return fmt.Errorf("input file is required")
}
if output == "" {
return fmt.Errorf("output file is required")
}
if _, err := os.Stat(input); err != nil {
return fmt.Errorf("input file does not exist: %w", err)
}
sshKey := selectSSHKey(sshKeyFlag, cfg)
var triedKeys []string
var success bool
if sshKey != "" {
triedKeys = append(triedKeys, sshKey)
log.WithFields(logrus.Fields{
"input": input,
"output": output,
"sshKey": sshKey,
}).Info("Trying decryption with provided SSH key")
if err := tryDecrypt(sshKey, output, input); err == nil {
log.Info("Decryption successful")
success = true
} else {
log.WithError(err).Warn("Decryption failed with provided SSH key")
}
} else {
keys, err := ScanSSHPrivateKeys()
if err != nil {
return fmt.Errorf("could not scan ~/.ssh for private keys: %w", err)
}
success = tryAllKeys(keys, input, output, log, &triedKeys)
}
if !success {
return fmt.Errorf("decryption failed: none of the tried SSH keys matched\nTried keys: %v", triedKeys)
}
return nil
},
}
cmd.Flags().StringP("input", "i", "", "Input file to decrypt")
cmd.Flags().StringP("output", "o", "", "Output file for decrypted data")
cmd.Flags().String("ssh-key", "", "SSH private key to use for decryption")
return cmd
}