-
Notifications
You must be signed in to change notification settings - Fork 0
/
v1.go
475 lines (413 loc) · 12.3 KB
/
v1.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
package seconf
import (
"bufio"
"crypto/rand"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/signal"
"strings"
"syscall"
"github.com/bgentry/speakeasy"
"golang.org/x/crypto/nacl/secretbox"
)
const keySize = 32
const nonceSize = 24
var pad = []byte("«lotsa jumPy f0x jump5 a11 ov3r»")
var hashbar = strings.Repeat("#", 80)
// Seconf is the struct for the seconf pathname and fields.
type Seconf struct {
ID int64
Path string
Args []string
Fields map[string]string
}
// NoBlank can be toggled to require a non-blank string for each field.
var NoBlank bool = false
/*
type Fielder struct {
Id int64
Name string
Password bool
}
*/
// returnHome is a cross-OS way of getting a HOMEDIR.
func returnHome() (homedir string) {
homedir = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if homedir == "" {
homedir = os.Getenv("USERPROFILE")
}
if homedir == "" {
homedir = os.Getenv("HOME")
}
return
}
// Locate uses returnHome to produce the location of the config file
func Locate(secustom string) (location string) {
return returnHome() + "/." + secustom
}
// Lock() is the new version of Create(), It returns any errors during the process instead of using os.Exit()
func Lock(secustom string, servicename string, arg ...string) error {
configfields := &Seconf{
Path: secustom,
Args: arg,
}
var m1 map[int]string = map[int]string{}
var newsplice []string
for k := range configfields.Args {
i := k
if len(configfields.Args[i]) > 4 {
if strings.Contains(configfields.Args[i], "pass") || strings.Contains(configfields.Args[i], "Pass") || strings.Contains(configfields.Args[i], "Key") || strings.Contains(configfields.Args[i], "key") || configfields.Args[i][0:4] == "pass" || configfields.Args[i][0:4] == "Pass" {
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ": ")
if m1[k] == "" {
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ": ")
}
if m1[k] == "" {
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ": ")
}
if m1[k] == "" {
return errors.New(configfields.Args[i] + " cannot be blank.")
}
} else {
m1[k] = Prompt(configfields.Args[i])
if m1[k] == "" {
fmt.Println("Can not be blank.")
m1[k] = Prompt(configfields.Args[i])
}
if m1[k] == "" {
fmt.Println("Can not be blank.")
m1[k] = Prompt(configfields.Args[i])
}
if m1[k] == "" {
return errors.New(configfields.Args[i] + " cannot be blank.")
}
}
} else {
m1[k] = Prompt(configfields.Args[i])
}
newsplice = append(newsplice, m1[k]+"::::")
}
configlock, _ := speakeasy.Ask("Create a password to encrypt config file:\nPress ENTER for no password.")
var userKey = configlock
var messagebox = strings.Join(newsplice, "")
messagebox = strings.TrimSuffix(messagebox, "::::")
var message = []byte(messagebox)
key := []byte(userKey)
key = append(key, pad...)
naclKey := new([keySize]byte)
copy(naclKey[:], key[:keySize])
nonce := new([nonceSize]byte)
// Read bytes from random and put them in nonce until it is full.
_, err := io.ReadFull(rand.Reader, nonce[:])
if err != nil {
return errors.New("Could not read from random: " + err.Error())
}
out := make([]byte, nonceSize)
copy(out, nonce[:])
out = secretbox.Seal(out, message, nonce, naclKey)
err = ioutil.WriteFile(returnHome()+"/."+secustom, out, 0600)
if err != nil {
return errors.New("Error while writing config file: " + err.Error())
}
fmt.Printf("Config file saved at "+returnHome()+"/."+secustom+" \nTotal size is %d bytes.\n", len(out))
return nil
}
// Pad sets the default pad, used to increase password length.
func Pad(s string) {
pad = []byte(s)
}
// Create initializes a new configuration file,
// at $HOME/secustom with the title servicename and
// as many fields as needed. Any field starting with
// "pass" will be assumed a password and input will not be echoed.
// Don't use Create(), use Lock() instead.
func Create(secustom string, servicename string, arg ...string) {
// Hopefully a clean exit
interrupt := make(chan os.Signal, 1)
stop := make(chan os.Signal, 1)
reload := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
signal.Notify(stop, syscall.SIGINT)
signal.Notify(reload, syscall.SIGHUP)
go func() {
select {
case signal := <-interrupt:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-reload:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-stop:
fmt.Printf("Got signal:%v\n", signal)
fmt.Println("Dying")
os.Exit(0)
}
}()
configfields := &Seconf{
Path: secustom,
Args: arg,
}
var m1 map[int]string = map[int]string{}
var newsplice []string
for k := range configfields.Args {
if len(configfields.Args[k]) > 3 {
if servicename == configfields.Args[k] {
servicename = ""
}
if strings.Contains(configfields.Args[k], "pass") || strings.Contains(configfields.Args[k], "Pass") || strings.Contains(configfields.Args[k], "Key") || strings.Contains(configfields.Args[k], "key") || configfields.Args[k][0:4] == "pass" || configfields.Args[k][0:4] == "Pass" {
//fmt.Printf("\nPress ENTER when you are finished typing. Will not echo.\n\n")
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[k] + " ")
if NoBlank == true {
if m1[k] == "" {
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[k] + ": ")
}
if m1[k] == "" {
m1[k], _ = speakeasy.Ask(servicename + " " + configfields.Args[k] + ": ")
}
if m1[k] == "" {
fmt.Println(configfields.Args[k] + " cannot be blank.")
return
}
}
} else {
m1[k] = Prompt(configfields.Args[k])
if NoBlank == true {
if m1[k] == "" {
fmt.Println("Can not be blank.")
m1[k] = Prompt(configfields.Args[k])
}
if m1[k] == "" {
fmt.Println("Can not be blank.")
m1[k] = Prompt(configfields.Args[k])
}
if m1[k] == "" {
fmt.Println(configfields.Args[k] + " cannot be blank.")
return
}
}
}
} else { // Handle single non password entries
m1[k] = Prompt(configfields.Args[k])
}
newsplice = append(newsplice, m1[k]+"::::")
}
configlock, _ := speakeasy.Ask("Create a password to encrypt config file:\nPress ENTER for no password\nConfig Password: ")
var userKey = configlock
var messagebox = strings.Join(newsplice, "")
messagebox = strings.TrimSuffix(messagebox, "::::")
var message = []byte(messagebox)
key := []byte(userKey)
key = append(key, pad...)
naclKey := new([keySize]byte)
copy(naclKey[:], key[:keySize])
nonce := new([nonceSize]byte)
// Read bytes from random and put them in nonce until it is full.
_, err := io.ReadFull(rand.Reader, nonce[:])
if err != nil {
fmt.Println("Could not read from random:", err)
return
}
out := make([]byte, nonceSize)
copy(out, nonce[:])
out = secretbox.Seal(out, message, nonce, naclKey)
err = ioutil.WriteFile(returnHome()+"/."+secustom, out, 0600)
if err != nil {
fmt.Println("Error while writing config file: ", err)
return
}
fmt.Printf("Config file saved at "+returnHome()+"/."+secustom+" \nTotal size is %d bytes.\n",
len(out))
os.Exit(0)
}
// Detect returns TRUE if a seconf file exists.
func Detect(secustom string) bool {
_, err := ioutil.ReadFile(returnHome() + "/." + secustom)
if err != nil {
return false
}
return true
}
// Read returns the decoded configuration file, or an error. Fields are separated by 4 colons. ("::::")
func Read(secustom string) (config string, err error) {
// This is the default encoded-but-not-safe blank password
naclKey := new([keySize]byte)
copy(naclKey[:], pad[:keySize])
nonce := new([nonceSize]byte)
in, err := ioutil.ReadFile(returnHome() + "/." + secustom)
if err != nil {
return "", err
}
copy(nonce[:], in[:nonceSize])
configbytes, ok := secretbox.Open(nil, in[nonceSize:], nonce, naclKey)
if ok {
return string(configbytes), nil
}
// The blank password didn't work. Ask the user via speakeasy
userKey, err := speakeasy.Ask("Password: ")
key := []byte(userKey)
key = append(key, pad...)
naclKey = new([keySize]byte)
copy(naclKey[:], key[:keySize])
nonce = new([nonceSize]byte)
in, err = ioutil.ReadFile(returnHome() + "/." + secustom)
if err != nil {
return "", err
}
copy(nonce[:], in[:nonceSize])
configbytes, ok = secretbox.Open(nil, in[nonceSize:], nonce, naclKey)
if !ok {
return "", errors.New("Could not decrypt the config file. Wrong password?")
}
return string(configbytes), nil
}
// constainsString returns true if a slice contains a string.
func containsString(slice []string, element string) bool {
return !(posString(slice, element) == -1)
}
// AskForConfirmation returns true if the user types one of the "okayResponses"
// See also: ConfirmChoice()
// https://gist.github.com/albrow/5882501
func AskForConfirmation() bool {
// Hopefully a clean exit
interrupt := make(chan os.Signal, 1)
stop := make(chan os.Signal, 1)
reload := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
signal.Notify(stop, syscall.SIGINT)
signal.Notify(reload, syscall.SIGHUP)
go func() {
select {
case signal := <-interrupt:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-reload:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-stop:
fmt.Printf("Got signal:%v\n", signal)
fmt.Println("Dying")
os.Exit(0)
}
}()
var response string
_, err := fmt.Scanln(&response)
if err != nil {
//fmt.Println(err)
return false
}
okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
nokayResponses := []string{"n", "N", "no", "No", "NO", "\n"}
quitResponses := []string{"q", "Q", "exit", "quit"}
if containsString(okayResponses, response) {
return true
} else if containsString(nokayResponses, response) {
return false
} else if containsString(quitResponses, response) {
return false
} else {
fmt.Println("\nNot valid answer, try again. [y/n] [yes/no]")
return AskForConfirmation()
}
}
// ConfirmChoice is like AskForConfirmation but with a default answer.
func ConfirmChoice(prompt string, def bool) bool {
// Hopefully a clean exit
interrupt := make(chan os.Signal, 1)
stop := make(chan os.Signal, 1)
reload := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
signal.Notify(stop, syscall.SIGINT)
signal.Notify(reload, syscall.SIGHUP)
go func() {
select {
case signal := <-interrupt:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-reload:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-stop:
fmt.Printf("Got signal:%v\n", signal)
fmt.Println("Dying")
os.Exit(0)
}
}()
var response string
fmt.Println(prompt)
if def {
fmt.Printf("[Y/n] ")
}
if !def {
fmt.Printf("[y/N] ")
}
_, err := fmt.Scanln(&response)
if err != nil {
//fmt.Println(err)
}
okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
nokayResponses := []string{"n", "N", "no", "No", "NO"}
quitResponses := []string{"q", "Q", "exit", "quit"}
if containsString(okayResponses, response) {
def = true
} else if containsString(nokayResponses, response) {
def = false
} else if containsString(quitResponses, response) {
os.Exit(1)
}
return def
}
// posString returns the first index of element in slice.
// If slice does not contain element, returns -1.
func posString(slice []string, element string) int {
for index, elem := range slice {
if elem == element {
return index
}
}
return -1
}
// Prompt the user for the particular field.
func Prompt(prompt string) string {
// Hopefully a clean exit
interrupt := make(chan os.Signal, 1)
stop := make(chan os.Signal, 1)
reload := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
signal.Notify(stop, syscall.SIGINT)
signal.Notify(reload, syscall.SIGHUP)
go func() {
select {
case signal := <-interrupt:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-reload:
fmt.Println("Got signal:", signal)
fmt.Println("Dying")
os.Exit(0)
case signal := <-stop:
fmt.Printf("Got signal:%v\n", signal)
fmt.Println("Dying")
os.Exit(0)
}
}()
fmt.Printf(prompt + ": ")
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
line := scanner.Text()
return line
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
return Prompt(prompt)
}
return ""
}