@@ -6,28 +6,73 @@ import (
6
6
"os"
7
7
"path/filepath"
8
8
"runtime"
9
- s "sort"
9
+ "sort"
10
+ "strconv"
10
11
"strings"
11
12
12
13
"github.com/hazcod/enpass-cli/pkg/clipboard"
13
14
"github.com/hazcod/enpass-cli/pkg/enpass"
15
+ "github.com/hazcod/enpass-cli/pkg/unlock"
14
16
"github.com/miquella/ask"
15
17
"github.com/sirupsen/logrus"
16
18
)
17
19
18
20
const (
19
- defaultLogLevel = logrus .InfoLevel
21
+ // commands
22
+ cmdVersion = "version"
23
+ cmdHelp = "help"
24
+ cmdDryRun = "dryrun"
25
+ cmdList = "list"
26
+ cmdShow = "show"
27
+ cmdCopy = "copy"
28
+ cmdPass = "pass"
29
+ // defaults
30
+ defaultLogLevel = logrus .InfoLevel
31
+ pinMinLength = 8
32
+ pinDefaultKdfIterCount = 100000
20
33
)
21
34
22
35
var (
23
36
// overwritten by go build
24
37
version = "dev"
25
- // enables prompts
26
- interactive = true
38
+ // set of all commands
39
+ commands = map [string ]struct {}{cmdVersion : {}, cmdHelp : {}, cmdDryRun : {}, cmdList : {},
40
+ cmdShow : {}, cmdCopy : {}, cmdPass : {}}
27
41
)
28
42
29
- func prompt (logger * logrus.Logger , msg string ) string {
30
- if interactive {
43
+ type Args struct {
44
+ command string
45
+ // params
46
+ filters []string
47
+ // flags
48
+ vaultPath * string
49
+ cardType * string
50
+ keyFilePath * string
51
+ logLevelStr * string
52
+ nonInteractive * bool
53
+ pinEnable * bool
54
+ sort * bool
55
+ trashed * bool
56
+ clipboardPrimary * bool
57
+ }
58
+
59
+ func (args * Args ) parse () {
60
+ args .vaultPath = flag .String ("vault" , "" , "Path to your Enpass vault." )
61
+ args .cardType = flag .String ("type" , "password" , "The type of your card. (password, ...)" )
62
+ args .keyFilePath = flag .String ("keyfile" , "" , "Path to your Enpass vault keyfile." )
63
+ args .logLevelStr = flag .String ("log" , defaultLogLevel .String (), "The log level from debug (5) to error (1)." )
64
+ args .nonInteractive = flag .Bool ("nonInteractive" , false , "Disable prompts and fail instead." )
65
+ args .pinEnable = flag .Bool ("pin" , false , "Enable PIN." )
66
+ args .sort = flag .Bool ("sort" , false , "Sort the output by title and username of the 'list' and 'show' command." )
67
+ args .trashed = flag .Bool ("trashed" , false , "Show trashed items in the 'list' and 'show' command." )
68
+ args .clipboardPrimary = flag .Bool ("clipboardPrimary" , false , "Use primary X selection instead of clipboard for the 'copy' command." )
69
+ flag .Parse ()
70
+ args .command = strings .ToLower (flag .Arg (0 ))
71
+ args .filters = flag .Args ()[1 :]
72
+ }
73
+
74
+ func prompt (logger * logrus.Logger , args * Args , msg string ) string {
75
+ if ! * args .nonInteractive {
31
76
if response , err := ask .HiddenAsk ("Enter " + msg + ": " ); err != nil {
32
77
logger .WithError (err ).Fatal ("could not prompt for " + msg )
33
78
} else {
@@ -37,27 +82,37 @@ func prompt(logger *logrus.Logger, msg string) string {
37
82
return ""
38
83
}
39
84
85
+ func printHelp () {
86
+ fmt .Print ("Valid commands: " )
87
+ for cmd := range commands {
88
+ fmt .Printf ("%s, " , cmd )
89
+ }
90
+ fmt .Println ()
91
+ flag .Usage ()
92
+ os .Exit (1 )
93
+ }
94
+
40
95
func sortEntries (cards []enpass.Card ) {
41
96
// Sort by username preserving original order
42
- s .SliceStable (cards , func (i , j int ) bool {
97
+ sort .SliceStable (cards , func (i , j int ) bool {
43
98
return strings .ToLower (cards [i ].Subtitle ) < strings .ToLower (cards [j ].Subtitle )
44
99
})
45
100
// Sort by title, preserving username order
46
- s .SliceStable (cards , func (i , j int ) bool {
101
+ sort .SliceStable (cards , func (i , j int ) bool {
47
102
return strings .ToLower (cards [i ].Title ) < strings .ToLower (cards [j ].Title )
48
103
})
49
104
}
50
105
51
- func listEntries (logger * logrus.Logger , vault * enpass.Vault , cardType string , sort bool , trashed bool , filters [] string ) {
52
- cards , err := vault .GetEntries (cardType , filters )
106
+ func listEntries (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
107
+ cards , err := vault .GetEntries (* args . cardType , args . filters )
53
108
if err != nil {
54
109
logger .WithError (err ).Fatal ("could not retrieve cards" )
55
110
}
56
- if sort {
111
+ if * args . sort {
57
112
sortEntries (cards )
58
113
}
59
114
for _ , card := range cards {
60
- if card .IsTrashed () && ! trashed {
115
+ if card .IsTrashed () && ! * args . trashed {
61
116
continue
62
117
}
63
118
logger .Printf (
@@ -71,19 +126,19 @@ func listEntries(logger *logrus.Logger, vault *enpass.Vault, cardType string, so
71
126
}
72
127
}
73
128
74
- func showEntries (logger * logrus.Logger , vault * enpass.Vault , cardType string , sort bool , trashed bool , filters [] string ) {
75
- cards , err := vault .GetEntries (cardType , filters )
129
+ func showEntries (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
130
+ cards , err := vault .GetEntries (* args . cardType , args . filters )
76
131
if err != nil {
77
132
logger .WithError (err ).Fatal ("could not retrieve cards" )
78
133
}
79
- if sort {
134
+ if * args . sort {
80
135
sortEntries (cards )
81
136
}
82
137
for _ , card := range cards {
83
- if card .IsTrashed () && ! trashed {
138
+ if card .IsTrashed () && ! * args . trashed {
84
139
continue
85
140
}
86
- password , err := card .Decrypt ()
141
+ decrypted , err := card .Decrypt ()
87
142
if err != nil {
88
143
logger .WithError (err ).Error ("could not decrypt " + card .Title )
89
144
continue
@@ -97,113 +152,166 @@ func showEntries(logger *logrus.Logger, vault *enpass.Vault, cardType string, so
97
152
card .Title ,
98
153
card .Subtitle ,
99
154
card .Category ,
100
- password ,
155
+ decrypted ,
101
156
)
102
157
}
103
158
}
104
159
105
- func copyEntry (logger * logrus.Logger , vault * enpass.Vault , cardType string , filters [] string ) {
106
- card , err := vault .GetUniqueEntry ( cardType , filters )
160
+ func copyEntry (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
161
+ card , err := vault .GetEntry ( * args . cardType , args . filters , true )
107
162
if err != nil {
108
163
logger .WithError (err ).Fatal ("could not retrieve unique card" )
109
164
}
110
165
111
- password , err := card .Decrypt ()
166
+ decrypted , err := card .Decrypt ()
112
167
if err != nil {
113
168
logger .WithError (err ).Fatal ("could not decrypt card" )
114
169
}
115
170
116
- if err := clipboard .WriteAll (password ); err != nil {
171
+ if * args .clipboardPrimary {
172
+ clipboard .Primary = true
173
+ logger .Debug ("primary X selection enabled" )
174
+ }
175
+
176
+ if err := clipboard .WriteAll (decrypted ); err != nil {
117
177
logger .WithError (err ).Fatal ("could not copy password to clipboard" )
118
178
}
119
179
}
120
180
121
- func entryPassword (logger * logrus.Logger , vault * enpass.Vault , cardType string , filters [] string ) {
122
- card , err := vault .GetUniqueEntry ( cardType , filters )
181
+ func entryPassword (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
182
+ card , err := vault .GetEntry ( * args . cardType , args . filters , true )
123
183
if err != nil {
124
184
logger .WithError (err ).Fatal ("could not retrieve unique card" )
125
185
}
126
186
127
- if password , err := card .Decrypt (); err != nil {
187
+ if decrypted , err := card .Decrypt (); err != nil {
128
188
logger .WithError (err ).Fatal ("could not decrypt card" )
129
189
} else {
130
- fmt .Println (password )
190
+ fmt .Println (decrypted )
131
191
}
132
192
}
133
193
134
- func main () {
135
- vaultPath := flag .String ("vault" , "" , "Path to your Enpass vault." )
136
- cardType := flag .String ("type" , "password" , "The type of your card. (password, ...)" )
137
- keyFilePath := flag .String ("keyfile" , "" , "Path to your Enpass vault keyfile." )
138
- logLevelStr := flag .String ("log" , defaultLogLevel .String (), "The log level from debug (5) to error (1)." )
139
- nonInteractive := flag .Bool ("nonInteractive" , false , "Disable prompts and fail instead." )
140
- sort := flag .Bool ("sort" , false , "Sort the output by title and username of the 'list' and 'show' command." )
141
- trashed := flag .Bool ("trashed" , false , "Show trashed items in the 'list' and 'show' command." )
142
- clipboardPrimary := flag .Bool ("clipboardPrimary" , false , "Use primary X selection instead of clipboard for the 'copy' command." )
194
+ func assembleVaultCredentials (logger * logrus.Logger , args * Args , store * unlock.SecureStore ) * enpass.VaultCredentials {
195
+ credentials := & enpass.VaultCredentials {
196
+ Password : os .Getenv ("MASTERPW" ),
197
+ KeyfilePath : * args .keyFilePath ,
198
+ }
143
199
144
- flag .Parse ()
200
+ if ! credentials .IsComplete () && store != nil {
201
+ var err error
202
+ if credentials .DBKey , err = store .Read (); err != nil {
203
+ logger .WithError (err ).Fatal ("could not read credentials from store" )
204
+ }
205
+ logger .Debug ("read credentials from store" )
206
+ }
207
+
208
+ if ! credentials .IsComplete () {
209
+ credentials .Password = prompt (logger , args , "master password" )
210
+ }
211
+
212
+ return credentials
213
+ }
214
+
215
+ func initializeStore (logger * logrus.Logger , args * Args ) * unlock.SecureStore {
216
+ vaultPath , _ := filepath .EvalSymlinks (* args .vaultPath )
217
+ store , err := unlock .NewSecureStore (filepath .Base (vaultPath ), logger .Level )
218
+ if err != nil {
219
+ logger .WithError (err ).Fatal ("could not create store" )
220
+ }
221
+
222
+ pin := os .Getenv ("ENP_PIN" )
223
+ if pin == "" {
224
+ pin = prompt (logger , args , "PIN" )
225
+ }
226
+ if len (pin ) < pinMinLength {
227
+ logger .Fatal ("PIN too short" )
228
+ }
229
+
230
+ pepper := os .Getenv ("ENP_PIN_PEPPER" )
231
+
232
+ pinKdfIterCount , err := strconv .ParseInt (os .Getenv ("ENP_PIN_ITER_COUNT" ), 10 , 32 )
233
+ if err != nil {
234
+ pinKdfIterCount = pinDefaultKdfIterCount
235
+ }
145
236
146
- if flag .NArg () == 0 {
147
- fmt .Println ("Specify a command: version, list, show, copy, pass" )
148
- flag .Usage ()
149
- os .Exit (1 )
237
+ if err := store .GeneratePassphrase (pin , pepper , int (pinKdfIterCount )); err != nil {
238
+ logger .WithError (err ).Fatal ("could not initialize store" )
150
239
}
151
240
152
- logLevel , err := logrus .ParseLevel (* logLevelStr )
241
+ return store
242
+ }
243
+
244
+ func main () {
245
+ args := & Args {}
246
+ args .parse ()
247
+
248
+ logLevel , err := logrus .ParseLevel (* args .logLevelStr )
153
249
if err != nil {
154
250
logrus .WithError (err ).Fatal ("invalid log level specified" )
155
251
}
156
252
logger := logrus .New ()
157
253
logger .SetLevel (logLevel )
158
254
159
- command := strings .ToLower (flag .Arg (0 ))
160
- filters := flag .Args ()[1 :]
161
-
162
- interactive = ! * nonInteractive
163
-
164
- if * clipboardPrimary {
165
- clipboard .Primary = true
166
- logger .Debug ("primary X selection enabled" )
255
+ if _ , contains := commands [args .command ]; ! contains {
256
+ printHelp ()
257
+ logger .Exit (1 )
167
258
}
168
259
169
- if command == "version" {
260
+ switch args .command {
261
+ case cmdHelp :
262
+ printHelp ()
263
+ return
264
+ case cmdVersion :
170
265
logger .Printf (
171
266
"%s arch=%s os=%s version=%s" ,
172
267
filepath .Base (os .Args [0 ]), runtime .GOARCH , runtime .GOOS , version ,
173
268
)
174
269
return
175
270
}
176
271
177
- masterPassword := os . Getenv ( "MASTERPW" )
178
- if masterPassword == "" {
179
- masterPassword = prompt ( logger , "master password " )
272
+ vault , err := enpass . NewVault ( * args . vaultPath , logger . Level )
273
+ if err != nil {
274
+ logger . WithError ( err ). Fatal ( "could not create vault " )
180
275
}
181
276
182
- if masterPassword == "" {
183
- logger .Fatal ("no master password provided. (via cli or MASTERPW env variable)" )
277
+ var store * unlock.SecureStore
278
+ if ! * args .pinEnable {
279
+ logger .Debug ("PIN disabled" )
280
+ } else {
281
+ logger .Debug ("PIN enabled, using store" )
282
+ store = initializeStore (logger , args )
283
+ logger .Debug ("initialized store" )
184
284
}
185
285
186
- vault := enpass.Vault {Logger : * logrus .New ()}
187
- vault .Logger .SetLevel (logger .Level )
286
+ credentials := assembleVaultCredentials (logger , args , store )
188
287
189
- if err := vault .Initialize (* vaultPath , * keyFilePath , masterPassword ); err != nil {
288
+ defer func () {
289
+ vault .Close ()
290
+ }()
291
+ if err := vault .Open (credentials ); err != nil {
190
292
logger .WithError (err ).Error ("could not open vault" )
191
293
logger .Exit (2 )
192
294
}
193
- defer func () { _ = vault .Close () }()
194
-
195
- logger .Debug ("initialized vault" )
295
+ logger .Debug ("opened vault" )
196
296
197
- switch command {
198
- case "list" :
199
- listEntries (logger , & vault , * cardType , * sort , * trashed , filters )
200
- case "show" :
201
- showEntries (logger , & vault , * cardType , * sort , * trashed , filters )
202
- case "copy" :
203
- copyEntry (logger , & vault , * cardType , filters )
204
- case "pass" :
205
- entryPassword (logger , & vault , * cardType , filters )
297
+ switch args .command {
298
+ case cmdDryRun :
299
+ logger .Debug ("dry run complete" ) // just init vault and store without doing anything
300
+ case cmdList :
301
+ listEntries (logger , vault , args )
302
+ case cmdShow :
303
+ showEntries (logger , vault , args )
304
+ case cmdCopy :
305
+ copyEntry (logger , vault , args )
306
+ case cmdPass :
307
+ entryPassword (logger , vault , args )
206
308
default :
207
- logger .WithField ("command" , command ).Fatal ("unknown command" )
309
+ logger .WithField ("command" , args .command ).Fatal ("unknown command" )
310
+ }
311
+
312
+ if store != nil {
313
+ if err := store .Write (credentials .DBKey ); err != nil {
314
+ logger .WithError (err ).Fatal ("failed to write credentials to store" )
315
+ }
208
316
}
209
317
}
0 commit comments