@@ -6,28 +6,73 @@ import (
66 "os"
77 "path/filepath"
88 "runtime"
9- s "sort"
9+ "sort"
10+ "strconv"
1011 "strings"
1112
1213 "github.com/hazcod/enpass-cli/pkg/clipboard"
1314 "github.com/hazcod/enpass-cli/pkg/enpass"
15+ "github.com/hazcod/enpass-cli/pkg/unlock"
1416 "github.com/miquella/ask"
1517 "github.com/sirupsen/logrus"
1618)
1719
1820const (
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
2033)
2134
2235var (
2336 // overwritten by go build
2437 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 : {}}
2741)
2842
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 {
3176 if response , err := ask .HiddenAsk ("Enter " + msg + ": " ); err != nil {
3277 logger .WithError (err ).Fatal ("could not prompt for " + msg )
3378 } else {
@@ -37,27 +82,37 @@ func prompt(logger *logrus.Logger, msg string) string {
3782 return ""
3883}
3984
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+
4095func sortEntries (cards []enpass.Card ) {
4196 // Sort by username preserving original order
42- s .SliceStable (cards , func (i , j int ) bool {
97+ sort .SliceStable (cards , func (i , j int ) bool {
4398 return strings .ToLower (cards [i ].Subtitle ) < strings .ToLower (cards [j ].Subtitle )
4499 })
45100 // Sort by title, preserving username order
46- s .SliceStable (cards , func (i , j int ) bool {
101+ sort .SliceStable (cards , func (i , j int ) bool {
47102 return strings .ToLower (cards [i ].Title ) < strings .ToLower (cards [j ].Title )
48103 })
49104}
50105
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 )
53108 if err != nil {
54109 logger .WithError (err ).Fatal ("could not retrieve cards" )
55110 }
56- if sort {
111+ if * args . sort {
57112 sortEntries (cards )
58113 }
59114 for _ , card := range cards {
60- if card .IsTrashed () && ! trashed {
115+ if card .IsTrashed () && ! * args . trashed {
61116 continue
62117 }
63118 logger .Printf (
@@ -71,19 +126,19 @@ func listEntries(logger *logrus.Logger, vault *enpass.Vault, cardType string, so
71126 }
72127}
73128
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 )
76131 if err != nil {
77132 logger .WithError (err ).Fatal ("could not retrieve cards" )
78133 }
79- if sort {
134+ if * args . sort {
80135 sortEntries (cards )
81136 }
82137 for _ , card := range cards {
83- if card .IsTrashed () && ! trashed {
138+ if card .IsTrashed () && ! * args . trashed {
84139 continue
85140 }
86- password , err := card .Decrypt ()
141+ decrypted , err := card .Decrypt ()
87142 if err != nil {
88143 logger .WithError (err ).Error ("could not decrypt " + card .Title )
89144 continue
@@ -97,113 +152,166 @@ func showEntries(logger *logrus.Logger, vault *enpass.Vault, cardType string, so
97152 card .Title ,
98153 card .Subtitle ,
99154 card .Category ,
100- password ,
155+ decrypted ,
101156 )
102157 }
103158}
104159
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 )
107162 if err != nil {
108163 logger .WithError (err ).Fatal ("could not retrieve unique card" )
109164 }
110165
111- password , err := card .Decrypt ()
166+ decrypted , err := card .Decrypt ()
112167 if err != nil {
113168 logger .WithError (err ).Fatal ("could not decrypt card" )
114169 }
115170
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 {
117177 logger .WithError (err ).Fatal ("could not copy password to clipboard" )
118178 }
119179}
120180
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 )
123183 if err != nil {
124184 logger .WithError (err ).Fatal ("could not retrieve unique card" )
125185 }
126186
127- if password , err := card .Decrypt (); err != nil {
187+ if decrypted , err := card .Decrypt (); err != nil {
128188 logger .WithError (err ).Fatal ("could not decrypt card" )
129189 } else {
130- fmt .Println (password )
190+ fmt .Println (decrypted )
131191 }
132192}
133193
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+ }
143199
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+ }
145236
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" )
150239 }
151240
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 )
153249 if err != nil {
154250 logrus .WithError (err ).Fatal ("invalid log level specified" )
155251 }
156252 logger := logrus .New ()
157253 logger .SetLevel (logLevel )
158254
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 )
167258 }
168259
169- if command == "version" {
260+ switch args .command {
261+ case cmdHelp :
262+ printHelp ()
263+ return
264+ case cmdVersion :
170265 logger .Printf (
171266 "%s arch=%s os=%s version=%s" ,
172267 filepath .Base (os .Args [0 ]), runtime .GOARCH , runtime .GOOS , version ,
173268 )
174269 return
175270 }
176271
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 " )
180275 }
181276
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" )
184284 }
185285
186- vault := enpass.Vault {Logger : * logrus .New ()}
187- vault .Logger .SetLevel (logger .Level )
286+ credentials := assembleVaultCredentials (logger , args , store )
188287
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 {
190292 logger .WithError (err ).Error ("could not open vault" )
191293 logger .Exit (2 )
192294 }
193- defer func () { _ = vault .Close () }()
194-
195- logger .Debug ("initialized vault" )
295+ logger .Debug ("opened vault" )
196296
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 )
206308 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+ }
208316 }
209317}
0 commit comments