-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfile.go
141 lines (126 loc) · 3.3 KB
/
file.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
package keyring
import (
"io"
"os"
"path/filepath"
"strings"
"filippo.io/age"
"github.com/launchrctl/launchr"
)
// CredentialsFile is an interface to open and edit credentials file.
type CredentialsFile interface {
io.ReadWriteCloser
// Open opens a file in FS with flag open options and perm for file permissions if the file is new.
// See os.OpenFile for more info about flag and perm arguments.
Open(flag int, perm os.FileMode) error
// Unlock decrypts a file if supported.
Unlock(new bool) error
// Lock makes it to request Unlock again.
Lock()
// Remove deletes a file from FS.
Remove() error
}
type plainFile struct {
fname string
file io.ReadWriteCloser
}
func (f *plainFile) Open(flag int, perm os.FileMode) (err error) {
isCreate := flag&os.O_CREATE == os.O_CREATE
if isCreate {
err = launchr.EnsurePath(filepath.Dir(f.fname))
if err != nil {
return err
}
}
file, err := os.OpenFile(f.fname, flag, perm) //nolint:gosec
if err != nil {
return err
}
f.file = file
return nil
}
func (f *plainFile) Unlock(bool) (err error) { return nil }
func (f *plainFile) Lock() {}
func (f *plainFile) Read(p []byte) (n int, err error) { return f.file.Read(p) }
func (f *plainFile) Write(p []byte) (n int, err error) { return f.file.Write(p) }
func (f *plainFile) Close() error { return f.file.Close() }
func (f *plainFile) Remove() (err error) {
err = os.Remove(f.fname)
if os.IsNotExist(err) {
return nil
}
return err
}
type ageFile struct {
file *plainFile
askPass AskPass
passphrase string // @todo make sure it's compatible with ACL in the future
r io.Reader
w io.WriteCloser
}
func newAgeFile(fname string, askPass AskPass) CredentialsFile {
return &ageFile{
file: &plainFile{
fname: fname + ".age",
},
askPass: askPass,
}
}
func (f *ageFile) Open(flag int, perm os.FileMode) (err error) { return f.file.Open(flag, perm) }
func (f *ageFile) Remove() error { return f.file.Remove() }
func (f *ageFile) Lock() { f.passphrase = "" }
func (f *ageFile) Unlock(new bool) (err error) {
if f.passphrase != "" {
return nil
}
if new {
f.passphrase, err = f.askPass.NewPass()
} else {
f.passphrase, err = f.askPass.GetPass()
}
if err != nil {
return err
}
if f.passphrase == "" {
return ErrEmptyPass
}
return nil
}
func (f *ageFile) Read(p []byte) (n int, err error) {
if f.passphrase == "" {
panic("call Unlock first")
}
if f.r == nil {
// Error shouldn't appear because the passphrase is not empty.
id, _ := age.NewScryptIdentity(f.passphrase)
f.r, err = age.Decrypt(f.file, id)
if err != nil {
// The file is malformed, not age encrypted and can't be read.
if strings.Contains(err.Error(), "parsing age header:") {
return 0, ErrKeyringMalformed
}
return 0, err
}
}
return f.r.Read(p)
}
func (f *ageFile) Write(p []byte) (n int, err error) {
if f.passphrase == "" {
panic("call Unlock first")
}
if f.w == nil {
// Error shouldn't appear because the passphrase is not empty.
rcp, _ := age.NewScryptRecipient(f.passphrase)
f.w, err = age.Encrypt(f.file, rcp)
if err != nil {
return 0, err
}
}
return f.w.Write(p)
}
func (f *ageFile) Close() error {
if f.w != nil {
_ = f.w.Close()
}
return f.file.Close()
}