diff --git a/acquisition/acquisition.go b/acquisition/acquisition.go index 08cbda9..62ca169 100644 --- a/acquisition/acquisition.go +++ b/acquisition/acquisition.go @@ -81,11 +81,12 @@ func New(path string) (*Acquisition, error) { } acq.Collector = coll - // Try to initialize encrypted streaming mode + // Try to initialize streaming zip mode. If key.txt exists the stream is + // encrypted; otherwise it is written to an unencrypted zip archive. encWriter, err := NewEncryptedZipWriter(acq.UUID) if err != nil { - // No key file or encryption setup failed, use normal mode - log.Debug("Encrypted streaming not available, using normal mode") + // Streaming setup failed, use normal mode + log.Debug("Zip streaming not available, using normal mode") acq.StreamingMode = false // Init logging file for normal mode @@ -96,8 +97,12 @@ func New(path string) (*Acquisition, error) { } acq.closeLog = closeLog } else { - // Encrypted streaming mode enabled - log.Info("Using encrypted streaming mode - data will be written directly to encrypted archive") + // Streaming mode enabled + if encWriter.IsEncrypted() { + log.Info("Using encrypted streaming mode - data will be written directly to encrypted archive") + } else { + log.Warning("Using unencrypted streaming mode - data will be written directly to an unencrypted zip archive") + } acq.StreamingMode = true acq.EncryptedWriter = encWriter @@ -123,14 +128,14 @@ func (a *Acquisition) Complete() { // Handle streaming mode completion if a.StreamingMode && a.EncryptedWriter != nil { - // Store acquisition info in the encrypted zip + // Store acquisition info in the streaming zip info, err := json.MarshalIndent(a, "", " ") if err != nil { - log.Error("Failed to marshal acquisition info for encrypted archive") + log.Error("Failed to marshal acquisition info for streaming archive") } else { err = a.EncryptedWriter.CreateFileFromBytes("acquisition.json", info) if err != nil { - log.ErrorExc("Failed to store acquisition info in encrypted archive", err) + log.ErrorExc("Failed to store acquisition info in streaming archive", err) } } @@ -140,23 +145,23 @@ func (a *Acquisition) Complete() { a.closeLog() } - // Write buffered command.log to encrypted archive + // Write buffered command.log to streaming archive if a.logBuffer != nil && a.logBuffer.Len() > 0 { err = a.EncryptedWriter.CreateFileFromBytes("command.log", a.logBuffer.Bytes()) if err != nil { - log.ErrorExc("Failed to add command.log to encrypted archive", err) + log.ErrorExc("Failed to add command.log to streaming archive", err) } } err = a.EncryptedWriter.CreateHashList() if err != nil { - log.ErrorExc("Failed to add hashes.csv to encrypted archive", err) + log.ErrorExc("Failed to add hashes.csv to streaming archive", err) } - // Close the encrypted writer + // Close the streaming writer err = a.EncryptedWriter.Close() if err != nil { - log.ErrorExc("Failed to close encrypted archive", err) + log.ErrorExc("Failed to close streaming archive", err) } // Remove the temporary storage directory if it was created and used @@ -222,9 +227,10 @@ func (a *Acquisition) GetSystemInformation() error { } func (a *Acquisition) HashFiles() error { - // In streaming mode, files are directly encrypted and no local files exist to hash + // In streaming mode, files are written directly to the archive and no local + // files exist to hash. if a.StreamingMode { - log.Debug("Skipping hash generation in streaming mode (data is encrypted)") + log.Debug("Skipping hash generation in streaming mode (data is archived directly)") return nil } @@ -290,7 +296,7 @@ func (a *Acquisition) StoreInfo() error { return nil } -// StreamAPKToZip streams an APK file directly to encrypted zip with certificate processing +// StreamAPKToZip streams an APK file directly to the streaming zip with certificate processing func (a *Acquisition) StreamAPKToZip(remotePath, zipPath string, processFunc func(io.Reader) error) error { if err := a.validateStreamingMode(); err != nil { return err @@ -317,16 +323,16 @@ func (a *Acquisition) StreamAPKToZip(remotePath, zipPath string, processFunc fun } } - // Stream to encrypted zip + // Stream to zip err = a.EncryptedWriter.CreateFileFromReader(zipPath, buffer.Reader()) if err != nil { - return fmt.Errorf("failed to add APK %q to encrypted zip: %v", remotePath, err) + return fmt.Errorf("failed to add APK %q to zip: %v", remotePath, err) } return nil } -// StreamBackupToZip streams a backup directly to encrypted zip +// StreamBackupToZip streams a backup directly to the streaming zip func (a *Acquisition) StreamBackupToZip(arg, zipPath string) error { if err := a.validateStreamingMode(); err != nil { return err @@ -354,7 +360,7 @@ func (a *Acquisition) StreamBackupToZip(arg, zipPath string) error { return nil } -// StreamBugreportToZip streams a bugreport directly to encrypted zip +// StreamBugreportToZip streams a bugreport directly to the streaming zip func (a *Acquisition) StreamBugreportToZip(zipPath string) error { if err := a.validateStreamingMode(); err != nil { return err @@ -385,7 +391,7 @@ func (a *Acquisition) validateStreamingMode() error { return fmt.Errorf("streaming mode not enabled") } if a.EncryptedWriter == nil { - return fmt.Errorf("encrypted writer not initialized") + return fmt.Errorf("zip writer not initialized") } if a.StreamingPuller == nil { return fmt.Errorf("streaming puller not initialized") diff --git a/acquisition/encrypted_stream.go b/acquisition/encrypted_stream.go index 15122c8..9eba1d2 100644 --- a/acquisition/encrypted_stream.go +++ b/acquisition/encrypted_stream.go @@ -33,6 +33,7 @@ type EncryptedZipWriter struct { outputPath string closed bool hashes []*zipHash + encrypted bool } type zipHash struct { @@ -53,14 +54,19 @@ func (hw *hashingWriter) Write(p []byte) (int, error) { return n, err } -// NewEncryptedZipWriter creates a new encrypted zip writer if key.txt exists +// NewEncryptedZipWriter creates a new streaming zip writer. +// If key.txt exists next to the executable, the zip stream is encrypted with +// age and written as .zip.age. If key.txt is missing, the zip stream is +// written unencrypted as .zip. func NewEncryptedZipWriter(uuid string) (*EncryptedZipWriter, error) { cwd := saveRuntime.GetExecutableDirectory() keyFilePath := filepath.Join(cwd, "key.txt") - // Check if key file exists if _, err := os.Stat(keyFilePath); os.IsNotExist(err) { - return nil, fmt.Errorf("key.txt not found, encrypted streaming not available") + log.Info("No age public key found, using unencrypted zip streaming mode.") + return newPlainZipWriter(cwd, uuid) + } else if err != nil { + return nil, fmt.Errorf("failed to check key.txt: %v", err) } log.Info("Found age public key, using encrypted streaming mode.") @@ -105,6 +111,29 @@ func NewEncryptedZipWriter(uuid string) (*EncryptedZipWriter, error) { zipWriter: zipWriter, outputPath: outputPath, closed: false, + encrypted: true, + }, nil +} + +func newPlainZipWriter(cwd, uuid string) (*EncryptedZipWriter, error) { + zipFileName := fmt.Sprintf("%s.zip", uuid) + outputPath := filepath.Join(cwd, zipFileName) + + file, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return nil, fmt.Errorf("failed to create output file: %v", err) + } + + zipWriter := zip.NewWriter(file) + + log.Infof("Started unencrypted zip streaming to %s", outputPath) + + return &EncryptedZipWriter{ + file: file, + zipWriter: zipWriter, + outputPath: outputPath, + closed: false, + encrypted: false, }, nil } @@ -250,7 +279,7 @@ func (ezw *EncryptedZipWriter) CreateFileFromPath(name, filePath string) error { return ezw.CreateFileFromReader(name, file) } -// Close finalizes and closes the encrypted zip +// Close finalizes and closes the streaming zip func (ezw *EncryptedZipWriter) Close() error { if ezw.closed { return nil @@ -264,10 +293,12 @@ func (ezw *EncryptedZipWriter) Close() error { lastErr = fmt.Errorf("failed to close zip writer: %v", err) } - // Close encryption writer - if err := ezw.encWriter.Close(); err != nil { - if lastErr == nil { - lastErr = fmt.Errorf("failed to close encryption writer: %v", err) + if ezw.encWriter != nil { + // Close encryption writer + if err := ezw.encWriter.Close(); err != nil { + if lastErr == nil { + lastErr = fmt.Errorf("failed to close encryption writer: %v", err) + } } } @@ -279,7 +310,11 @@ func (ezw *EncryptedZipWriter) Close() error { } if lastErr == nil { - log.Infof("Encrypted archive created successfully at %s", ezw.outputPath) + if ezw.encrypted { + log.Infof("Encrypted archive created successfully at %s", ezw.outputPath) + } else { + log.Infof("Unencrypted zip archive created successfully at %s", ezw.outputPath) + } } return lastErr } @@ -294,10 +329,15 @@ func (ezw *EncryptedZipWriter) IsClosed() bool { return ezw.closed } +// IsEncrypted returns whether the zip stream is encrypted with age. +func (ezw *EncryptedZipWriter) IsEncrypted() bool { + return ezw.encrypted +} + // checkClosed is a helper method to check if the writer is closed func (ezw *EncryptedZipWriter) checkClosed() error { if ezw.closed { - return fmt.Errorf("encrypted zip writer is closed") + return fmt.Errorf("zip writer is closed") } return nil } diff --git a/main.go b/main.go index 124961c..96678eb 100644 --- a/main.go +++ b/main.go @@ -152,8 +152,12 @@ func main() { } if acq.StreamingMode { - // In streaming mode, all data is already encrypted in the zip stream - log.Info("Finalizing encrypted acquisition...") + // In streaming mode, all data is already in the zip stream. + if acq.EncryptedWriter != nil && acq.EncryptedWriter.IsEncrypted() { + log.Info("Finalizing encrypted acquisition...") + } else { + log.Warning("Finalizing unencrypted zip acquisition...") + } } else { // Traditional mode: hash files, then encrypt if key exists err = acq.HashFiles()