Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 27 additions & 21 deletions acquisition/acquisition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
60 changes: 50 additions & 10 deletions acquisition/encrypted_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type EncryptedZipWriter struct {
outputPath string
closed bool
hashes []*zipHash
encrypted bool
}

type zipHash struct {
Expand All @@ -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 <uuid>.zip.age. If key.txt is missing, the zip stream is
// written unencrypted as <uuid>.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.")
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}
}

Expand All @@ -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
}
Expand All @@ -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
}
8 changes: 6 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading