1package chezmoi 2 3import ( 4 "os" 5 "os/exec" 6 "runtime" 7 8 "go.uber.org/multierr" 9 10 "github.com/twpayne/chezmoi/v2/internal/chezmoilog" 11) 12 13// A GPGEncryption uses gpg for encryption and decryption. See https://gnupg.org/. 14type GPGEncryption struct { 15 Command string 16 Args []string 17 Recipient string 18 Symmetric bool 19 Suffix string 20} 21 22// Decrypt implements Encyrption.Decrypt. 23func (e *GPGEncryption) Decrypt(ciphertext []byte) ([]byte, error) { 24 var plaintext []byte 25 if err := withPrivateTempDir(func(tempDirAbsPath AbsPath) error { 26 ciphertextAbsPath := tempDirAbsPath.JoinString("ciphertext" + e.EncryptedSuffix()) 27 if err := os.WriteFile(ciphertextAbsPath.String(), ciphertext, 0o600); err != nil { 28 return err 29 } 30 plaintextAbsPath := tempDirAbsPath.JoinString("plaintext") 31 32 args := e.decryptArgs(plaintextAbsPath, ciphertextAbsPath) 33 if err := e.run(args); err != nil { 34 return err 35 } 36 37 var err error 38 plaintext, err = os.ReadFile(plaintextAbsPath.String()) 39 return err 40 }); err != nil { 41 return nil, err 42 } 43 return plaintext, nil 44} 45 46// DecryptToFile implements Encryption.DecryptToFile. 47func (e *GPGEncryption) DecryptToFile(plaintextFilename AbsPath, ciphertext []byte) error { 48 return withPrivateTempDir(func(tempDirAbsPath AbsPath) error { 49 ciphertextAbsPath := tempDirAbsPath.JoinString("ciphertext" + e.EncryptedSuffix()) 50 if err := os.WriteFile(ciphertextAbsPath.String(), ciphertext, 0o600); err != nil { 51 return err 52 } 53 args := e.decryptArgs(plaintextFilename, ciphertextAbsPath) 54 return e.run(args) 55 }) 56} 57 58// Encrypt implements Encryption.Encrypt. 59func (e *GPGEncryption) Encrypt(plaintext []byte) ([]byte, error) { 60 var ciphertext []byte 61 if err := withPrivateTempDir(func(tempDirAbsPath AbsPath) error { 62 plaintextAbsPath := tempDirAbsPath.JoinString("plaintext") 63 if err := os.WriteFile(plaintextAbsPath.String(), plaintext, 0o600); err != nil { 64 return err 65 } 66 ciphertextAbsPath := tempDirAbsPath.JoinString("ciphertext" + e.EncryptedSuffix()) 67 68 args := e.encryptArgs(plaintextAbsPath, ciphertextAbsPath) 69 if err := e.run(args); err != nil { 70 return err 71 } 72 73 var err error 74 ciphertext, err = os.ReadFile(ciphertextAbsPath.String()) 75 return err 76 }); err != nil { 77 return nil, err 78 } 79 return ciphertext, nil 80} 81 82// EncryptFile implements Encryption.EncryptFile. 83func (e *GPGEncryption) EncryptFile(plaintextFilename AbsPath) ([]byte, error) { 84 var ciphertext []byte 85 if err := withPrivateTempDir(func(tempDirAbsPath AbsPath) error { 86 ciphertextAbsPath := tempDirAbsPath.JoinString("ciphertext" + e.EncryptedSuffix()) 87 88 args := e.encryptArgs(plaintextFilename, ciphertextAbsPath) 89 if err := e.run(args); err != nil { 90 return err 91 } 92 93 var err error 94 ciphertext, err = os.ReadFile(ciphertextAbsPath.String()) 95 return err 96 }); err != nil { 97 return nil, err 98 } 99 return ciphertext, nil 100} 101 102// EncryptedSuffix implements Encryption.EncryptedSuffix. 103func (e *GPGEncryption) EncryptedSuffix() string { 104 return e.Suffix 105} 106 107// decryptArgs returns the arguments for decryption. 108func (e *GPGEncryption) decryptArgs(plaintextFilename, ciphertextFilename AbsPath) []string { 109 args := []string{"--output", plaintextFilename.String()} 110 args = append(args, e.Args...) 111 args = append(args, "--decrypt", ciphertextFilename.String()) 112 return args 113} 114 115// encryptArgs returns the arguments for encryption. 116func (e *GPGEncryption) encryptArgs(plaintextFilename, ciphertextFilename AbsPath) []string { 117 args := []string{ 118 "--armor", 119 "--output", ciphertextFilename.String(), 120 } 121 if e.Symmetric { 122 args = append(args, "--symmetric") 123 } else if e.Recipient != "" { 124 args = append(args, "--recipient", e.Recipient) 125 } 126 args = append(args, e.Args...) 127 if !e.Symmetric { 128 args = append(args, "--encrypt") 129 } 130 args = append(args, plaintextFilename.String()) 131 return args 132} 133 134// run runs the command with args. 135func (e *GPGEncryption) run(args []string) error { 136 //nolint:gosec 137 cmd := exec.Command(e.Command, args...) 138 cmd.Stdin = os.Stdin 139 cmd.Stdout = os.Stdout 140 cmd.Stderr = os.Stderr 141 return chezmoilog.LogCmdRun(cmd) 142} 143 144// withPrivateTempDir creates a private temporary and calls f. 145func withPrivateTempDir(f func(tempDirAbsPath AbsPath) error) (err error) { 146 var tempDir string 147 if tempDir, err = os.MkdirTemp("", "chezmoi-encryption"); err != nil { 148 return 149 } 150 defer func() { 151 err = multierr.Append(err, os.RemoveAll(tempDir)) 152 }() 153 if runtime.GOOS != "windows" { 154 if err = os.Chmod(tempDir, 0o700); err != nil { 155 return 156 } 157 } 158 159 err = f(NewAbsPath(tempDir)) 160 return 161} 162