/
config.go
155 lines (125 loc) · 3.94 KB
/
config.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
package client
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/chzyer/readline"
sargon2 "github.com/mdouchement/simple-argon2"
"github.com/mdouchement/standardfile/pkg/libsf"
"github.com/pkg/errors"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
)
const (
saltKeyLength = 16
credentialsfile = ".standardfile"
)
// A Config holds client's configuration.
type Config struct {
Endpoint string `json:"endpoint"`
Email string `json:"email"`
BearerToken string `json:"bearer_token"` // JWT used before 20200115
Session libsf.Session `json:"session"` // Since 20200115
KeyChain libsf.KeyChain `json:"keychain"`
}
// Remove removes the credential files from the current directory.
func Remove() error {
return os.Remove(credentialsfile)
}
// Load gets the configuration from the current folder according to `credentialsfile` const.
func Load() (Config, error) {
fmt.Println("Loading credentials from " + credentialsfile)
cfg := Config{
KeyChain: libsf.KeyChain{
ItemsKey: make(map[string]string),
},
}
ciphertext, err := os.ReadFile(credentialsfile)
if err != nil {
return cfg, errors.Wrap(err, "could not read credentials file")
}
//
// Key derivation of passphrase
passphrase, err := readline.Password("passphrase: ")
if err != nil {
return cfg, errors.Wrap(err, "could not read passphrase from stdin")
}
salt := ciphertext[:saltKeyLength]
ciphertext = ciphertext[saltKeyLength:]
hash := argon2.IDKey(passphrase, salt, 3, 64<<10, 2, 32)
//
// Seal config
aead, err := chacha20poly1305.NewX(hash)
if err != nil {
return cfg, errors.Wrap(err, "could not create AEAD")
}
nonce := ciphertext[:aead.NonceSize()]
ciphertext = ciphertext[aead.NonceSize():]
payload, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
return cfg, errors.Wrap(err, "could not decrypt credentials file")
}
err = json.Unmarshal(payload, &cfg)
if err != nil {
return cfg, errors.Wrap(err, "could not parse config")
}
return cfg, nil
}
// Save stores the configuration in the current folder according to `credentialsfile` const.
func Save(cfg Config) error {
payload, err := json.Marshal(cfg)
if err != nil {
return errors.Wrap(err, "could not serialize config")
}
fmt.Println("Storing credentials in current directory as " + credentialsfile)
passphrase, err := readline.Password("passphrase: ")
if err != nil {
return errors.Wrap(err, "could not read passphrase from stdin")
}
//
// Key derivation of passphrase
salt, err := sargon2.GenerateRandomBytes(saltKeyLength)
if err != nil {
return errors.Wrap(err, "could not generate salt for credentials")
}
hash := argon2.IDKey(passphrase, salt, 3, 64<<10, 2, 32)
//
// Seal config
aead, err := chacha20poly1305.NewX(hash)
if err != nil {
return errors.Wrap(err, "could not create AEAD")
}
nonce, err := sargon2.GenerateRandomBytes(uint32(aead.NonceSize()))
if err != nil {
return errors.Wrap(err, "could not generate nonce for credentials")
}
ciphertext := aead.Seal(nil, nonce, payload, nil)
ciphertext = append(nonce, ciphertext...)
ciphertext = append(salt, ciphertext...)
f, err := os.Create(credentialsfile)
if err != nil {
return errors.Wrapf(err, "could not create %s", credentialsfile)
}
defer f.Close()
_, err = f.Write(ciphertext)
if err != nil {
return errors.Wrap(err, "could not store credentials")
}
return errors.Wrap(f.Sync(), "could not store credentials")
}
// Refresh refreshes the session if needed.
func Refresh(client libsf.Client, cfg *Config) error {
if !cfg.Session.AccessExpiredAt(time.Now().Add(time.Hour)) {
return nil
}
fmt.Println("Refreshing the session")
session, err := client.RefreshSession(cfg.Session.AccessToken, cfg.Session.RefreshToken)
if err != nil {
return errors.Wrap(err, "could not refresh session")
}
cfg.Session = *session
client.SetSession(cfg.Session)
err = Save(*cfg)
return errors.Wrap(err, "could not save refreshed session")
}