Skip to content

Commit 96d6a03

Browse files
committed
improve store passphrase generation
1 parent f35d863 commit 96d6a03

File tree

4 files changed

+76
-47
lines changed

4 files changed

+76
-47
lines changed

cmd/enpasscli/main.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,11 @@ func main() {
155155
return
156156
}
157157

158-
accessData := &enpass.VaultAccessData{}
159-
accessData.KeyfilePath = *keyFilePath
160-
accessData.Password = os.Getenv("MASTERPW")
158+
accessData := &enpass.VaultAccessData{
159+
VaultPath: *vaultPath,
160+
KeyfilePath: *keyFilePath,
161+
Password: os.Getenv("MASTERPW"),
162+
}
161163

162164
var store *pin.SecureStore
163165
if !*pinEnabled {
@@ -173,7 +175,7 @@ func main() {
173175
vault := enpass.Vault{Logger: *logrus.New()}
174176
vault.Logger.SetLevel(logger.Level)
175177

176-
if err := vault.Initialize(*vaultPath, accessData); err != nil {
178+
if err := vault.Initialize(accessData); err != nil {
177179
logger.WithError(err).Error("could not open vault")
178180
logger.Exit(2)
179181
}
@@ -209,13 +211,12 @@ func initAndReadSecureStore(logger *logrus.Logger, accessData *enpass.VaultAcces
209211
storePin = prompt(logger, "PIN")
210212
}
211213
logger.Debug("initialising secure store")
212-
store, err := pin.NewSecureStore(storePin)
214+
store, err := pin.NewSecureStore(storePin, accessData.VaultPath)
213215
if err != nil {
214216
logger.WithError(err).Fatal("could not initialise secure store")
215217
}
216218
logger.Debug("reading from store")
217219
if accessData.DBKey, err = store.Read(); err != nil {
218-
// TODO debug ?
219220
logger.WithError(err).Fatal("could not read access data from store")
220221
}
221222
return store

pkg/enpass/vault.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ type Vault struct {
4343
}
4444

4545
type VaultAccessData struct {
46+
VaultPath string
4647
KeyfilePath string
4748
Password string
4849
DBKey []byte
4950
}
5051

5152
func (accessData *VaultAccessData) IsComplete() bool {
52-
return accessData.Password != "" || accessData.DBKey != nil
53+
return accessData.VaultPath != "" &&
54+
(accessData.Password != "" || accessData.DBKey != nil)
5355
}
5456

5557
func (v *Vault) openEncryptedDatabase(path string, dbKey []byte) (err error) {
@@ -76,7 +78,7 @@ func (v *Vault) generateAndSetDBKey(accessData *VaultAccessData) error {
7678
}
7779

7880
if accessData.Password == "" {
79-
return errors.New("empty v password provided")
81+
return errors.New("empty vault password provided")
8082
}
8183

8284
if accessData.KeyfilePath == "" && v.vaultInfo.HasKeyfile == 1 {
@@ -88,7 +90,7 @@ func (v *Vault) generateAndSetDBKey(accessData *VaultAccessData) error {
8890
v.Logger.Debug("generating master password")
8991
masterPassword, err := v.generateMasterPassword([]byte(accessData.Password), accessData.KeyfilePath)
9092
if err != nil {
91-
return errors.Wrap(err, "could not generate v unlock key")
93+
return errors.Wrap(err, "could not generate vault unlock key")
9294
}
9395

9496
v.Logger.Debug("extracting salt from database")
@@ -119,17 +121,17 @@ func (v *Vault) checkPaths() error {
119121
}
120122

121123
// Initialize : setup a connection to the Enpass database. Call this before doing anything.
122-
func (v *Vault) Initialize(databasePath string, accessData *VaultAccessData) error {
123-
if databasePath == "" {
124-
return errors.New("empty v path provided")
124+
func (v *Vault) Initialize(accessData *VaultAccessData) error {
125+
if accessData.VaultPath == "" {
126+
return errors.New("empty vault path provided")
125127
}
126128

127-
v.databaseFilename = filepath.Join(databasePath, vaultFileName)
128-
v.vaultInfoFilename = filepath.Join(databasePath, vaultInfoFileName)
129+
v.databaseFilename = filepath.Join(accessData.VaultPath, vaultFileName)
130+
v.vaultInfoFilename = filepath.Join(accessData.VaultPath, vaultInfoFileName)
129131

130-
v.Logger.Debug("checking provided v paths")
132+
v.Logger.Debug("checking provided vault paths")
131133
if err := v.checkPaths(); err != nil {
132-
return errors.Wrap(err, "invalid v path provided")
134+
return errors.Wrap(err, "invalid vault path provided")
133135
}
134136

135137
v.Logger.Debug("loading vault info")

pkg/pin/aes256gcm.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@ const (
1818
minKdfIterCount = 10000
1919
)
2020

21-
func deriveKey(passphrase string, salt []byte, kdfIterCount int) ([]byte, []byte) {
22-
if salt == nil {
23-
salt = make([]byte, saltLength)
24-
rand.Read(salt) // Salt http://www.ietf.org/rfc/rfc2898.txt
25-
}
21+
func sha256sum(data []byte) []byte {
22+
sum := sha256.Sum256(data)
23+
return sum[:]
24+
}
25+
26+
func generateSalt() []byte {
27+
salt := make([]byte, saltLength)
28+
rand.Read(salt) // Salt http://www.ietf.org/rfc/rfc2898.txt
29+
return salt
30+
}
31+
32+
func deriveKey(passphrase []byte, salt []byte, kdfIterCount int) []byte {
2633
if kdfIterCount < minKdfIterCount {
2734
kdfIterCount = minKdfIterCount
2835
}
29-
return pbkdf2.Key([]byte(passphrase), salt, kdfIterCount, sha256.Size, sha256.New), salt
36+
return pbkdf2.Key(passphrase, salt, kdfIterCount, sha256.Size, sha256.New)
3037
}
3138

3239
func createCipherGCM(key []byte) (cipher.AEAD, error) {
@@ -37,8 +44,9 @@ func createCipherGCM(key []byte) (cipher.AEAD, error) {
3744
return cipher.NewGCM(b)
3845
}
3946

40-
func encrypt(passphrase string, plaintext []byte, kdfIterCount int) (string, error) {
41-
key, salt := deriveKey(passphrase, nil, kdfIterCount)
47+
func encrypt(passphrase []byte, plaintext []byte, kdfIterCount int) (string, error) {
48+
salt := generateSalt()
49+
key := deriveKey(passphrase, salt, kdfIterCount)
4250
iv := make([]byte, 12)
4351
rand.Read(iv) // Section 8.2 http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
4452
aesgcm, err := createCipherGCM(key)
@@ -49,12 +57,12 @@ func encrypt(passphrase string, plaintext []byte, kdfIterCount int) (string, err
4957
return hex.EncodeToString(salt) + "-" + hex.EncodeToString(iv) + "-" + hex.EncodeToString(data), nil
5058
}
5159

52-
func decrypt(passphrase, ciphertext string, kdfIterCount int) ([]byte, error) {
60+
func decrypt(passphrase []byte, ciphertext string, kdfIterCount int) ([]byte, error) {
5361
arr := strings.Split(ciphertext, "-")
5462
salt, _ := hex.DecodeString(arr[0])
5563
iv, _ := hex.DecodeString(arr[1])
5664
data, _ := hex.DecodeString(arr[2])
57-
key, _ := deriveKey(passphrase, salt, kdfIterCount)
65+
key := deriveKey(passphrase, salt, kdfIterCount)
5866
aesgcm, err := createCipherGCM(key)
5967
if err != nil {
6068
return nil, err

pkg/pin/securestore.go

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,72 @@
11
package pin
22

33
import (
4-
"errors"
4+
"bytes"
55
"os"
6+
"os/exec"
67
"path/filepath"
8+
9+
"github.com/pkg/errors"
710
)
811

912
const (
10-
storeFileName = "enpasscli.mpw"
11-
kdfIterCount = 100000
13+
fileNamePref = "enpasscli-"
14+
fileMode = 0600
15+
kdfIterCount = 100000
16+
minPinLength = 8
1217
)
1318

1419
type SecureStore struct {
1520
filePath string
16-
passphrase string
21+
passphrase []byte
1722
wasReadSuccessfully bool
1823
}
1924

20-
func NewSecureStore(pin string) (*SecureStore, error) {
21-
dirPath := os.Getenv("XDG_RUNTIME_DIR")
22-
if dirPath == "" {
23-
dirPath = os.TempDir()
25+
func NewSecureStore(pin string, vaultPath string) (*SecureStore, error) {
26+
if len(pin) < minPinLength {
27+
return nil, errors.New("PIN too short")
2428
}
25-
// TODO check dir read/writeablility ?
26-
if passphrase, err := generatePassphrase(pin); err != nil {
29+
file, err := getOrCreateStoreFile(vaultPath)
30+
if err != nil {
31+
return nil, errors.Wrap(err, "could not create store file")
32+
}
33+
if passphrase, err := generatePassphrase(pin, file); err != nil {
2734
return nil, err
2835
} else {
2936
return &SecureStore{
30-
filePath: filepath.Join(dirPath, storeFileName),
37+
filePath: file.Name(),
3138
passphrase: passphrase,
3239
wasReadSuccessfully: false,
3340
}, nil
3441
}
3542
}
3643

37-
func generatePassphrase(pin string) (string, error) {
38-
if pin == "" {
39-
return "", errors.New("PIN not set")
44+
func getOrCreateStoreFile(vaultPath string) (*os.File, error) {
45+
dirPath := os.Getenv("XDG_RUNTIME_DIR")
46+
if dirPath == "" {
47+
dirPath = os.TempDir()
48+
}
49+
fileName := fileNamePref + filepath.Base(vaultPath)
50+
return os.OpenFile(filepath.Join(dirPath, fileName), os.O_CREATE, fileMode)
51+
}
52+
53+
// this is more obscurity than security but can make trivial attack vectors more difficult
54+
func generatePassphrase(pin string, file *os.File) ([]byte, error) {
55+
data := []byte(pin)
56+
lastboot, err := exec.Command("who", "-b").Output()
57+
if err != nil {
58+
return nil, errors.Wrap(err, "could not retrieve last boot time")
4059
}
41-
// TODO check PIN length / quality
42-
// TODO calc passphrase
43-
return pin, nil
60+
data = append(data, bytes.TrimSpace(lastboot)...)
61+
return sha256sum(data), nil
4462
}
4563

4664
func (store *SecureStore) Read() ([]byte, error) {
47-
if store.passphrase == "" {
65+
if store.passphrase == nil {
4866
return nil, errors.New("empty store passphrase")
4967
}
5068
data, _ := os.ReadFile(store.filePath)
51-
if data == nil {
69+
if data == nil || len(data) == 0 {
5270
return nil, nil // nothing to read
5371
}
5472
dbKey, err := decrypt(store.passphrase, string(data), kdfIterCount)
@@ -63,14 +81,14 @@ func (store *SecureStore) Write(dbKey []byte) error {
6381
if store.wasReadSuccessfully {
6482
return nil // no need to overwrite the file if read was already successful
6583
}
66-
if store.passphrase == "" {
84+
if store.passphrase == nil {
6785
return errors.New("empty store passphrase")
6886
}
6987
data, err := encrypt(store.passphrase, dbKey, kdfIterCount)
7088
if err != nil {
7189
return err
7290
}
73-
return os.WriteFile(store.filePath, []byte(data), 0600)
91+
return os.WriteFile(store.filePath, []byte(data), fileMode)
7492
}
7593

7694
func (store *SecureStore) Clear() error {

0 commit comments

Comments
 (0)