forked from gopasspw/gopass
/
gpg.go
165 lines (139 loc) · 4.03 KB
/
gpg.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package cli
import (
"bufio"
"bytes"
"context"
"os"
"os/exec"
"strings"
"github.com/gopasspw/gopass/pkg/backend/crypto/gpg"
"github.com/gopasspw/gopass/pkg/out"
lru "github.com/hashicorp/golang-lru"
)
var (
// defaultArgs contains the default GPG args for non-interactive use. Note: Do not use '--batch'
// as this will disable (necessary) passphrase questions!
defaultArgs = []string{"--quiet", "--yes", "--compress-algo=none", "--no-encrypt-to", "--no-auto-check-trustdb"}
// Ext is the file extension used by this backend
Ext = "gpg"
// IDFile is the name of the recipients file used by this backend
IDFile = ".gpg-id"
)
// GPG is a gpg wrapper
type GPG struct {
binary string
args []string
pubKeys gpg.KeyList
privKeys gpg.KeyList
listCache *lru.TwoQueueCache
}
// Config is the gpg wrapper config
type Config struct {
Binary string
Args []string
Umask int
}
// New creates a new GPG wrapper
func New(ctx context.Context, cfg Config) (*GPG, error) {
// ensure created files don't have group or world perms set
// this setting should be inherited by sub-processes
umask(cfg.Umask)
// make sure GPG_TTY is set (if possible)
if gt := os.Getenv("GPG_TTY"); gt == "" {
if t := tty(); t != "" {
_ = os.Setenv("GPG_TTY", t)
}
}
g := &GPG{
binary: "gpg",
args: append(defaultArgs, cfg.Args...),
}
cache, err := lru.New2Q(1024)
if err != nil {
return nil, err
}
g.listCache = cache
bin, err := Binary(ctx, cfg.Binary)
if err != nil {
return nil, err
}
g.binary = bin
return g, nil
}
// RecipientIDs returns a list of recipient IDs for a given file
func (g *GPG) RecipientIDs(ctx context.Context, buf []byte) ([]string, error) {
_ = os.Setenv("LANGUAGE", "C")
recp := make([]string, 0, 5)
args := []string{"--batch", "--list-only", "--list-packets", "--no-default-keyring", "--secret-keyring", "/dev/null"}
cmd := exec.CommandContext(ctx, g.binary, args...)
cmd.Stdin = bytes.NewReader(buf)
out.Debug(ctx, "gpg.GetRecipients: %s %+v", cmd.Path, cmd.Args)
cmdout, err := cmd.CombinedOutput()
if err != nil {
return []string{}, err
}
scanner := bufio.NewScanner(bytes.NewBuffer(cmdout))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
out.Debug(ctx, "gpg Output: %s", line)
if !strings.HasPrefix(line, ":pubkey enc packet:") {
continue
}
m := splitPacket(line)
if keyid, found := m["keyid"]; found {
kl, err := g.listKeys(ctx, "public", keyid)
if err != nil || len(kl) < 1 {
continue
}
recp = append(recp, kl[0].Fingerprint)
}
}
return recp, nil
}
// Encrypt will encrypt the given content for the recipients. If alwaysTrust is true
// the trust-model will be set to always as to avoid (annoying) "unusable public key"
// errors when encrypting.
func (g *GPG) Encrypt(ctx context.Context, plaintext []byte, recipients []string) ([]byte, error) {
args := append(g.args, "--encrypt")
if gpg.IsAlwaysTrust(ctx) {
// changing the trustmodel is possibly dangerous. A user should always
// explicitly opt-in to do this
args = append(args, "--trust-model=always")
}
for _, r := range recipients {
args = append(args, "--recipient", r)
}
buf := &bytes.Buffer{}
cmd := exec.CommandContext(ctx, g.binary, args...)
cmd.Stdin = bytes.NewReader(plaintext)
cmd.Stdout = buf
cmd.Stderr = os.Stderr
out.Debug(ctx, "gpg.Encrypt: %s %+v", cmd.Path, cmd.Args)
err := cmd.Run()
return buf.Bytes(), err
}
// Decrypt will try to decrypt the given file
func (g *GPG) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
args := append(g.args, "--decrypt")
cmd := exec.CommandContext(ctx, g.binary, args...)
cmd.Stdin = bytes.NewReader(ciphertext)
cmd.Stderr = os.Stderr
out.Debug(ctx, "gpg.Decrypt: %s %+v", cmd.Path, cmd.Args)
return cmd.Output()
}
// Initialized always returns nil
func (g *GPG) Initialized(ctx context.Context) error {
return nil
}
// Name returns gpg
func (g *GPG) Name() string {
return "gpg"
}
// Ext returns gpg
func (g *GPG) Ext() string {
return Ext
}
// IDFile returns .gpg-id
func (g *GPG) IDFile() string {
return IDFile
}