Skip to content

Commit c8ba59b

Browse files
authored
Use package-friendly runtime paths (#104)
1 parent bda6835 commit c8ba59b

12 files changed

Lines changed: 229 additions & 32 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Before launching androidqf you need to have the target Android device connected
102102

103103
Once USB debugging is enabled, you can proceed launching androidqf. It will first attempt to connect to the device over the USB bridge, which should result in the Android phone to prompt you to manually authorize the host keys. Make sure to authorize them, ideally permanently so that the prompt wouldn't appear again.
104104

105-
Now androidqf should be executing and creating an acquisition folder at the same path you have placed your androidqf binary. At some point in the execution, androidqf will prompt you some choices: these prompts will pause the acquisition until you provide a selection, so pay attention.
105+
Now androidqf should be executing and creating an acquisition folder in your current working directory. At some point in the execution, androidqf will prompt you some choices: these prompts will pause the acquisition until you provide a selection, so pay attention.
106106

107107
The following data can be extracted:
108108

@@ -186,7 +186,7 @@ Ideally you should have the drive fully encrypted, but that might not always be
186186

187187
Alternatively, androidqf allows to encrypt each acquisition with a provided [age](https://age-encryption.org) public key. Preferably, this public key belongs to a keypair for which the end-user does not possess, or at least carry, the private key. In this way, the end-user would not be able to decrypt the acquired data even under duress.
188188

189-
If you place a file called `key.txt` in the same folder as the androidqf executable, androidqf will automatically attempt to compress and encrypt each acquisition and delete the original unencrypted copies.
189+
If you place a file called `key.txt` in the current working directory, androidqf will automatically attempt to compress and encrypt each acquisition and delete the original unencrypted copies. androidqf also checks for `key.txt` in the same folder as the executable; if both files exist, the current working directory takes precedence.
190190

191191
Once you have retrieved an encrypted acquisition file, you can decrypt it with age like so:
192192

acquisition/acquisition_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,34 @@ func TestCompleteDoesNotOverwriteExistingCompletedTimestamp(t *testing.T) {
5252
t.Fatalf("Complete() changed Completed from %s to %s", completed, acq.Completed)
5353
}
5454
}
55+
56+
func TestStoreSecurelyUsesCurrentWorkingDirectory(t *testing.T) {
57+
cwd := t.TempDir()
58+
t.Chdir(cwd)
59+
writeTestAgeKey(t, cwd)
60+
61+
storagePath := filepath.Join(cwd, "test-acquisition")
62+
if err := os.Mkdir(storagePath, 0o755); err != nil {
63+
t.Fatalf("Mkdir(storagePath) error = %v", err)
64+
}
65+
if err := os.WriteFile(filepath.Join(storagePath, "data.txt"), []byte("evidence"), 0o600); err != nil {
66+
t.Fatalf("WriteFile(data.txt) error = %v", err)
67+
}
68+
69+
acq := &Acquisition{
70+
UUID: "test-acquisition",
71+
StoragePath: storagePath,
72+
}
73+
74+
if err := acq.StoreSecurely(); err != nil {
75+
t.Fatalf("StoreSecurely() error = %v", err)
76+
}
77+
78+
wantPath := filepath.Join(cwd, "test-acquisition.zip.age")
79+
if _, err := os.Stat(wantPath); err != nil {
80+
t.Fatalf("Stat(encrypted output) error = %v", err)
81+
}
82+
if _, err := os.Stat(storagePath); !os.IsNotExist(err) {
83+
t.Fatalf("storage path still exists or returned unexpected error: %v", err)
84+
}
85+
}

acquisition/encrypted_stream.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"time"
2222

2323
"filippo.io/age"
24-
saveRuntime "github.com/botherder/go-savetime/runtime"
2524
"github.com/mvt-project/androidqf/log"
2625
)
2726

@@ -55,11 +54,17 @@ func (hw *hashingWriter) Write(p []byte) (int, error) {
5554

5655
// NewEncryptedZipWriter creates a new encrypted zip writer if key.txt exists
5756
func NewEncryptedZipWriter(uuid string) (*EncryptedZipWriter, error) {
58-
cwd := saveRuntime.GetExecutableDirectory()
59-
keyFilePath := filepath.Join(cwd, "key.txt")
57+
cwd, err := os.Getwd()
58+
if err != nil {
59+
return nil, err
60+
}
6061

6162
// Check if key file exists
62-
if _, err := os.Stat(keyFilePath); os.IsNotExist(err) {
63+
keyFilePath, ok, err := findAgeKeyFile()
64+
if err != nil {
65+
return nil, err
66+
}
67+
if !ok {
6368
return nil, fmt.Errorf("key.txt not found, encrypted streaming not available")
6469
}
6570

acquisition/encrypted_stream_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import (
77
"encoding/csv"
88
"encoding/hex"
99
"io"
10+
"os"
11+
"path/filepath"
1012
"strings"
1113
"testing"
14+
15+
"filippo.io/age"
1216
)
1317

1418
func TestCreateHashListTracksPlaintextZipEntries(t *testing.T) {
@@ -93,6 +97,44 @@ func sha256Hex(content string) string {
9397
return hex.EncodeToString(sum[:])
9498
}
9599

100+
func writeTestAgeKey(t *testing.T, dir string) {
101+
t.Helper()
102+
103+
identity, err := age.GenerateX25519Identity()
104+
if err != nil {
105+
t.Fatalf("GenerateX25519Identity() error = %v", err)
106+
}
107+
108+
keyPath := filepath.Join(dir, "key.txt")
109+
if err := os.WriteFile(keyPath, []byte(identity.Recipient().String()), 0o600); err != nil {
110+
t.Fatalf("WriteFile(key.txt) error = %v", err)
111+
}
112+
}
113+
114+
func TestNewEncryptedZipWriterUsesCurrentWorkingDirectory(t *testing.T) {
115+
cwd := t.TempDir()
116+
t.Chdir(cwd)
117+
writeTestAgeKey(t, cwd)
118+
119+
ezw, err := NewEncryptedZipWriter("test-acquisition")
120+
if err != nil {
121+
t.Fatalf("NewEncryptedZipWriter() error = %v", err)
122+
}
123+
defer os.Remove(ezw.GetOutputPath())
124+
125+
if err := ezw.Close(); err != nil {
126+
t.Fatalf("Close() error = %v", err)
127+
}
128+
129+
wantPath := filepath.Join(cwd, "test-acquisition.zip.age")
130+
if ezw.GetOutputPath() != wantPath {
131+
t.Fatalf("output path = %q, want %q", ezw.GetOutputPath(), wantPath)
132+
}
133+
if _, err := os.Stat(wantPath); err != nil {
134+
t.Fatalf("Stat(output) error = %v", err)
135+
}
136+
}
137+
96138
func TestValidateZipEntryName(t *testing.T) {
97139
tests := []struct {
98140
name string

acquisition/key.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// androidqf - Android Quick Forensics
2+
// Copyright (c) 2021-2022 Claudio Guarnieri.
3+
// Use of this software is governed by the MVT License 1.1 that can be found at
4+
// https://license.mvt.re/1.1/
5+
6+
package acquisition
7+
8+
import (
9+
"os"
10+
"path/filepath"
11+
)
12+
13+
const keyFileName = "key.txt"
14+
15+
func findAgeKeyFile() (string, bool, error) {
16+
cwd, err := os.Getwd()
17+
if err != nil {
18+
return "", false, err
19+
}
20+
if keyPath, ok := findAgeKeyFileInDirs(cwd, ""); ok {
21+
return keyPath, true, nil
22+
}
23+
24+
exe, err := os.Executable()
25+
if err != nil {
26+
return "", false, err
27+
}
28+
29+
keyPath, ok := findAgeKeyFileInDirs(cwd, filepath.Dir(exe))
30+
return keyPath, ok, nil
31+
}
32+
33+
func findAgeKeyFileInDirs(cwd, executableDir string) (string, bool) {
34+
for _, dir := range []string{cwd, executableDir} {
35+
if dir == "" {
36+
continue
37+
}
38+
39+
keyPath := filepath.Join(dir, keyFileName)
40+
if _, err := os.Stat(keyPath); err == nil {
41+
return keyPath, true
42+
}
43+
}
44+
45+
return "", false
46+
}

acquisition/key_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package acquisition
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func TestFindAgeKeyFileInDirsPrefersCurrentWorkingDirectory(t *testing.T) {
10+
cwd := t.TempDir()
11+
executableDir := t.TempDir()
12+
13+
cwdKey := filepath.Join(cwd, keyFileName)
14+
executableKey := filepath.Join(executableDir, keyFileName)
15+
if err := os.WriteFile(cwdKey, []byte("cwd"), 0o600); err != nil {
16+
t.Fatalf("WriteFile(cwd key) error = %v", err)
17+
}
18+
if err := os.WriteFile(executableKey, []byte("exe"), 0o600); err != nil {
19+
t.Fatalf("WriteFile(executable key) error = %v", err)
20+
}
21+
22+
got, ok := findAgeKeyFileInDirs(cwd, executableDir)
23+
if !ok {
24+
t.Fatal("findAgeKeyFileInDirs() did not find a key")
25+
}
26+
if got != cwdKey {
27+
t.Fatalf("key path = %q, want %q", got, cwdKey)
28+
}
29+
}
30+
31+
func TestFindAgeKeyFileInDirsFallsBackToExecutableDirectory(t *testing.T) {
32+
cwd := t.TempDir()
33+
executableDir := t.TempDir()
34+
35+
executableKey := filepath.Join(executableDir, keyFileName)
36+
if err := os.WriteFile(executableKey, []byte("exe"), 0o600); err != nil {
37+
t.Fatalf("WriteFile(executable key) error = %v", err)
38+
}
39+
40+
got, ok := findAgeKeyFileInDirs(cwd, executableDir)
41+
if !ok {
42+
t.Fatal("findAgeKeyFileInDirs() did not find a key")
43+
}
44+
if got != executableKey {
45+
t.Fatalf("key path = %q, want %q", got, executableKey)
46+
}
47+
}

acquisition/secure.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"strings"
1515

1616
"filippo.io/age"
17-
saveRuntime "github.com/botherder/go-savetime/runtime"
1817
"github.com/mvt-project/androidqf/log"
1918
)
2019

@@ -44,10 +43,16 @@ func (a *Acquisition) StoreSecurely() error {
4443
return nil
4544
}
4645

47-
cwd := saveRuntime.GetExecutableDirectory()
46+
cwd, err := os.Getwd()
47+
if err != nil {
48+
return err
49+
}
4850

49-
keyFilePath := filepath.Join(cwd, "key.txt")
50-
if _, err := os.Stat(keyFilePath); os.IsNotExist(err) {
51+
keyFilePath, ok, err := findAgeKeyFile()
52+
if err != nil {
53+
return err
54+
}
55+
if !ok {
5156
return nil
5257
}
5358

@@ -58,7 +63,7 @@ func (a *Acquisition) StoreSecurely() error {
5863

5964
log.Info("Compressing the acquisition folder. This might take a while...")
6065

61-
err := createZipFile(a.StoragePath, zipFilePath)
66+
err = createZipFile(a.StoragePath, zipFilePath)
6267
if err != nil {
6368
return err
6469
}

adb/adb_darwin.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
func (a *ADB) findExe() error {
17-
err := assets.DeployAssets()
17+
assetDir, err := assets.DeployAssets()
1818
if err != nil {
1919
return err
2020
}
@@ -23,6 +23,8 @@ func (a *ADB) findExe() error {
2323
if err == nil {
2424
a.ExePath = adbPath
2525
return nil
26+
} else if assetDir != "" {
27+
a.ExePath = filepath.Join(assetDir, "adb")
2628
} else {
2729
a.ExePath = filepath.Join(saveRuntime.GetExecutableDirectory(), "adb")
2830
}

adb/adb_linux.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ import (
1414
)
1515

1616
func (a *ADB) findExe() error {
17-
err := assets.DeployAssets()
17+
assetDir, err := assets.DeployAssets()
1818
if err != nil {
1919
return err
2020
}
2121

2222
adbPath, err := exec.LookPath("adb")
2323
if err == nil {
2424
a.ExePath = adbPath
25+
} else if assetDir != "" {
26+
a.ExePath = filepath.Join(assetDir, "adb")
2527
} else {
2628
a.ExePath = filepath.Join(saveRuntime.GetExecutableDirectory(), "adb")
2729
}

adb/adb_windows.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ import (
1717

1818
func (a *ADB) findExe() error {
1919
// TODO: only deploy assets when needed
20-
err := assets.DeployAssets()
20+
assetDir, err := assets.DeployAssets()
2121
if err != nil {
2222
return err
2323
}
2424

2525
adbPath, err := exec.LookPath("adb.exe")
2626
if err == nil {
2727
a.ExePath = adbPath
28+
} else if assetDir != "" {
29+
a.ExePath = filepath.Join(assetDir, "adb.exe")
2830
} else {
2931
// Get path of the current directory
3032
ex, err := os.Executable()

0 commit comments

Comments
 (0)