@@ -16,9 +16,66 @@ import (
16
16
"filippo.io/age"
17
17
saveRuntime "github.com/botherder/go-savetime/runtime"
18
18
"github.com/mvt-project/androidqf/log"
19
+ "github.com/spf13/afero"
19
20
)
20
21
21
- func createZipFile (sourceDir , zipPath string ) error {
22
+ // createZipFromFs creates a zip file from any afero filesystem
23
+ func createZipFromFs (fs afero.Fs , sourceDir string , writer io.Writer ) error {
24
+ zipWriter := zip .NewWriter (writer )
25
+ defer zipWriter .Close ()
26
+
27
+ // Walk the filesystem and add all files to the zip
28
+ err := afero .Walk (fs , sourceDir , func (path string , info os.FileInfo , err error ) error {
29
+ if err != nil {
30
+ return err
31
+ }
32
+
33
+ // Skip directories
34
+ if info .IsDir () {
35
+ return nil
36
+ }
37
+
38
+ // Get relative path from sourceDir
39
+ relPath , err := filepath .Rel (sourceDir , path )
40
+ if err != nil {
41
+ return err
42
+ }
43
+
44
+ // Create the file header
45
+ header , err := zip .FileInfoHeader (info )
46
+ if err != nil {
47
+ return err
48
+ }
49
+ header .Name = filepath .ToSlash (relPath ) // Ensure forward slashes for zip compatibility
50
+ header .Method = zip .Deflate
51
+
52
+ // Create the file in the zip
53
+ writer , err := zipWriter .CreateHeader (header )
54
+ if err != nil {
55
+ return err
56
+ }
57
+
58
+ // Read the file content from the filesystem
59
+ file , err := fs .Open (path )
60
+ if err != nil {
61
+ return err
62
+ }
63
+ defer file .Close ()
64
+
65
+ // Copy file content to zip
66
+ _ , err = io .Copy (writer , file )
67
+ return err
68
+ })
69
+
70
+ if err != nil {
71
+ return fmt .Errorf ("failed to walk filesystem: %v" , err )
72
+ }
73
+
74
+ return zipWriter .Close ()
75
+ }
76
+
77
+ // createZipFromDisk creates a zip file from disk (legacy approach)
78
+ func createZipFromDisk (sourceDir , zipPath string ) error {
22
79
zipFile , err := os .Create (zipPath )
23
80
if err != nil {
24
81
return fmt .Errorf ("failed to create ZIP file: %v" , err )
@@ -39,83 +96,111 @@ func createZipFile(sourceDir, zipPath string) error {
39
96
}
40
97
41
98
func (a * Acquisition ) StoreSecurely () error {
42
- cwd := saveRuntime .GetExecutableDirectory ()
43
-
44
- keyFilePath := filepath .Join (cwd , "key.txt" )
45
- if _ , err := os .Stat (keyFilePath ); os .IsNotExist (err ) {
99
+ // Only proceed if key file is present
100
+ if ! a .KeyFilePresent {
46
101
return nil
47
102
}
48
103
49
- log .Info ("You provided an age public key, storing the acquisition securely." )
50
-
51
- zipFileName := fmt .Sprintf ("%s.zip" , a .UUID )
52
- zipFilePath := filepath .Join (cwd , zipFileName )
104
+ log .Info ("Age public key detected, storing the acquisition securely." )
53
105
54
- log .Info ("Compressing the acquisition folder. This might take a while..." )
55
-
56
- err := createZipFile (a .StoragePath , zipFilePath )
57
- if err != nil {
58
- return err
59
- }
60
-
61
- log .Info ("Encrypting the compressed archive. This might take a while..." )
106
+ cwd := saveRuntime .GetExecutableDirectory ()
107
+ keyFilePath := filepath .Join (cwd , "key.txt" )
62
108
109
+ // Read the public key
63
110
publicKey , err := os .ReadFile (keyFilePath )
64
111
if err != nil {
65
- return err
112
+ return fmt . Errorf ( "failed to read key file: %v" , err )
66
113
}
67
114
publicKeyStr := strings .TrimSpace (string (publicKey ))
68
115
116
+ // Parse the age recipient
69
117
recipient , err := age .ParseX25519Recipient (publicKeyStr )
70
118
if err != nil {
71
119
return fmt .Errorf ("failed to parse public key %q: %v" , publicKeyStr , err )
72
120
}
73
121
74
- zipFile , err := os .Open (zipFilePath )
75
- if err != nil {
76
- return err
77
- }
78
- defer zipFile .Close ()
79
-
80
- encFileName := fmt .Sprintf ("%s.age" , zipFileName )
122
+ // Create the encrypted file
123
+ encFileName := fmt .Sprintf ("%s.age" , a .UUID )
81
124
encFilePath := filepath .Join (cwd , encFileName )
82
- encFile , err := os .OpenFile (encFilePath , os .O_WRONLY | os .O_CREATE | os .O_APPEND , 0o600 )
125
+ encFile , err := os .OpenFile (encFilePath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , 0o600 )
83
126
if err != nil {
84
127
return fmt .Errorf ("unable to create encrypted file: %v" , err )
85
128
}
86
129
defer encFile .Close ()
87
130
88
- w , err := age .Encrypt (encFile , recipient )
131
+ // Create age encryptor
132
+ ageWriter , err := age .Encrypt (encFile , recipient )
89
133
if err != nil {
90
- return fmt .Errorf ("failed to create encrypted file : %v" , err )
134
+ return fmt .Errorf ("failed to create age encryptor : %v" , err )
91
135
}
92
136
93
- _ , err = io .Copy (w , zipFile )
94
- if err != nil {
95
- return fmt .Errorf ("failed to write to encrypted file: %v" , err )
137
+ if a .UseMemoryFs {
138
+ log .Info ("Compressing and encrypting acquisition data from memory. This might take a while..." )
139
+
140
+ // Create zip directly from memory filesystem and encrypt it
141
+ err = createZipFromFs (a .Fs , a .StoragePath , ageWriter )
142
+ if err != nil {
143
+ return fmt .Errorf ("failed to create encrypted zip from memory: %v" , err )
144
+ }
145
+ } else {
146
+ log .Info ("Compressing the acquisition folder. This might take a while..." )
147
+
148
+ // Create temporary zip file on disk first
149
+ zipFileName := fmt .Sprintf ("%s.zip" , a .UUID )
150
+ zipFilePath := filepath .Join (cwd , zipFileName )
151
+
152
+ err := createZipFromDisk (a .StoragePath , zipFilePath )
153
+ if err != nil {
154
+ return fmt .Errorf ("failed to create zip file: %v" , err )
155
+ }
156
+
157
+ log .Info ("Encrypting the compressed archive. This might take a while..." )
158
+
159
+ // Read the zip file and encrypt it
160
+ zipFile , err := os .Open (zipFilePath )
161
+ if err != nil {
162
+ return fmt .Errorf ("failed to open zip file: %v" , err )
163
+ }
164
+
165
+ _ , err = io .Copy (ageWriter , zipFile )
166
+ zipFile .Close ()
167
+
168
+ if err != nil {
169
+ return fmt .Errorf ("failed to encrypt zip file: %v" , err )
170
+ }
171
+
172
+ // Remove the temporary unencrypted zip file
173
+ err = os .Remove (zipFilePath )
174
+ if err != nil {
175
+ log .Warningf ("Failed to remove temporary zip file: %v" , err )
176
+ }
96
177
}
97
178
98
- if err := w .Close (); err != nil {
179
+ // Close the age writer
180
+ if err := ageWriter .Close (); err != nil {
99
181
return fmt .Errorf ("failed to close encrypted file: %v" , err )
100
182
}
101
183
102
184
log .Infof ("Acquisition successfully encrypted at %s" , encFilePath )
103
185
104
- // TODO: we should securely wipe the files.
105
- zipFile .Close ()
106
- err = os .Remove (zipFilePath )
107
- if err != nil {
108
- return fmt .Errorf ("failed to delete the unencrypted compressed archive: %v" , err )
109
- }
110
-
111
- // Ensure log file is closed before removing the acquisition directory
112
- if a .closeLog != nil {
113
- defer a .closeLog ()
114
- }
115
-
116
- err = os .RemoveAll (a .StoragePath )
117
- if err != nil {
118
- return fmt .Errorf ("failed to delete the original unencrypted acquisition folder: %v" , err )
186
+ // Clean up based on filesystem type
187
+ if a .UseMemoryFs {
188
+ log .Info ("Clearing in-memory data..." )
189
+ // For memory filesystem, we just need to ensure the log is closed
190
+ // The memory will be freed when the filesystem is dereferenced
191
+ } else {
192
+ log .Info ("Removing unencrypted acquisition folder..." )
193
+ // Ensure log file is closed before removing the acquisition directory
194
+ if a .closeLog != nil {
195
+ a .closeLog ()
196
+ a .closeLog = nil // Prevent double-close
197
+ }
198
+
199
+ // Remove the original unencrypted folder
200
+ err = os .RemoveAll (a .StoragePath )
201
+ if err != nil {
202
+ return fmt .Errorf ("failed to delete the original unencrypted acquisition folder: %v" , err )
203
+ }
119
204
}
120
205
121
206
return nil
0 commit comments