Skip to content

Commit c150b22

Browse files
committed
feat: refactor firefox get master key retrieval and decryption functions. (#300)
* refactor: Simplify Firefox master key retrieval and decryption functions. - Simplify variable names and functions in browsingdata/password/password.go - Modify Decrypt function for each PBE type to have only one parameter named globalSalt in crypto/crypto.go - Implement functions to retrieve master key from Firefox's key4.db file and query metadata and private NSS data in browser/firefox/firefox.go * chore: Add dependencies and tests to Firefox package. - Add go-sqlmock and github.com/kisielk/sqlstruct dependencies - Add tests for Firefox package metadata and nssPrivate query - Add test for Firefox's processMasterKey function (currently commented out) * refactor: Refactor Firefox test functions, remove unused code - Remove unused test function in firefox_test.go file - Clean up code by removing unnecessary changes - Simplify file structure for easier maintenance and readability
1 parent 6786deb commit c150b22

File tree

6 files changed

+152
-77
lines changed

6 files changed

+152
-77
lines changed

browser/firefox/firefox.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package firefox
22

33
import (
4+
"bytes"
5+
"database/sql"
46
"errors"
57
"fmt"
68
"io/fs"
9+
"os"
710
"path/filepath"
811

12+
_ "modernc.org/sqlite" // sqlite3 driver TODO: replace with chooseable driver
13+
914
"github.com/moond4rk/hackbrowserdata/browsingdata"
15+
"github.com/moond4rk/hackbrowserdata/crypto"
1016
"github.com/moond4rk/hackbrowserdata/item"
1117
"github.com/moond4rk/hackbrowserdata/utils/fileutil"
1218
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
@@ -68,8 +74,82 @@ func firefoxWalkFunc(items []item.Item, multiItemPaths map[string]map[item.Item]
6874
}
6975
}
7076

77+
// GetMasterKey returns master key of Firefox. from key4.db
7178
func (f *Firefox) GetMasterKey() ([]byte, error) {
72-
return f.masterKey, nil
79+
tempFilename := item.FirefoxKey4.TempFilename()
80+
81+
// Open and defer close of the database.
82+
keyDB, err := sql.Open("sqlite", tempFilename)
83+
if err != nil {
84+
return nil, fmt.Errorf("open key4.db error: %w", err)
85+
}
86+
defer os.Remove(tempFilename)
87+
defer keyDB.Close()
88+
89+
globalSalt, metaBytes, err := queryMetaData(keyDB)
90+
if err != nil {
91+
return nil, fmt.Errorf("query metadata error: %w", err)
92+
}
93+
94+
nssA11, nssA102, err := queryNssPrivate(keyDB)
95+
if err != nil {
96+
return nil, fmt.Errorf("query NSS private error: %w", err)
97+
}
98+
99+
return processMasterKey(globalSalt, metaBytes, nssA11, nssA102)
100+
}
101+
102+
func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
103+
const query = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
104+
var globalSalt, metaBytes []byte
105+
if err := db.QueryRow(query).Scan(&globalSalt, &metaBytes); err != nil {
106+
return nil, nil, err
107+
}
108+
return globalSalt, metaBytes, nil
109+
}
110+
111+
func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
112+
const query = `SELECT a11, a102 from nssPrivate`
113+
var nssA11, nssA102 []byte
114+
if err := db.QueryRow(query).Scan(&nssA11, &nssA102); err != nil {
115+
return nil, nil, err
116+
}
117+
return nssA11, nssA102, nil
118+
}
119+
120+
// processMasterKey process master key of Firefox.
121+
// Process the metaBytes and nssA11 with the corresponding cryptographic operations.
122+
func processMasterKey(globalSalt, metaBytes, nssA11, nssA102 []byte) ([]byte, error) {
123+
metaPBE, err := crypto.NewASN1PBE(metaBytes)
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
k, err := metaPBE.Decrypt(globalSalt)
129+
if err != nil {
130+
return nil, err
131+
}
132+
133+
if !bytes.Contains(k, []byte("password-check")) {
134+
return nil, errors.New("password-check not found")
135+
}
136+
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
137+
if !bytes.Equal(nssA102, keyLin) {
138+
return nil, errors.New("nssA102 not equal keyLin")
139+
}
140+
nssPBE, err := crypto.NewASN1PBE(nssA11)
141+
if err != nil {
142+
return nil, err
143+
}
144+
finallyKey, err := nssPBE.Decrypt(globalSalt)
145+
if err != nil {
146+
return nil, err
147+
}
148+
if len(finallyKey) < 24 {
149+
return nil, errors.New("finallyKey length less than 24")
150+
}
151+
finallyKey = finallyKey[:24]
152+
return finallyKey, nil
73153
}
74154

75155
func (f *Firefox) Name() string {

browser/firefox/firefox_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package firefox
2+
3+
import (
4+
"testing"
5+
6+
"github.com/DATA-DOG/go-sqlmock"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestQueryMetaData(t *testing.T) {
11+
db, mock, err := sqlmock.New()
12+
assert.NoError(t, err)
13+
defer db.Close()
14+
15+
rows := sqlmock.NewRows([]string{"item1", "item2"}).
16+
AddRow([]byte("globalSalt"), []byte("metaBytes"))
17+
mock.ExpectQuery("SELECT item1, item2 FROM metaData WHERE id = 'password'").WillReturnRows(rows)
18+
19+
globalSalt, metaBytes, err := queryMetaData(db)
20+
assert.NoError(t, err)
21+
assert.Equal(t, []byte("globalSalt"), globalSalt)
22+
assert.Equal(t, []byte("metaBytes"), metaBytes)
23+
}
24+
25+
func TestQueryNssPrivate(t *testing.T) {
26+
db, mock, err := sqlmock.New()
27+
assert.NoError(t, err)
28+
defer db.Close()
29+
30+
rows := sqlmock.NewRows([]string{"a11", "a102"}).
31+
AddRow([]byte("nssA11"), []byte("nssA102"))
32+
mock.ExpectQuery("SELECT a11, a102 from nssPrivate").WillReturnRows(rows)
33+
34+
nssA11, nssA102, err := queryNssPrivate(db)
35+
assert.NoError(t, err)
36+
assert.Equal(t, []byte("nssA11"), nssA11)
37+
assert.Equal(t, []byte("nssA102"), nssA102)
38+
}

browsingdata/password/password.go

Lines changed: 24 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package password
22

33
import (
4-
"bytes"
54
"database/sql"
65
"encoding/base64"
76
"log/slog"
@@ -169,87 +168,42 @@ const (
169168
)
170169

171170
func (f *FirefoxPassword) Parse(masterKey []byte) error {
172-
globalSalt, metaBytes, nssA11, nssA102, err := getFirefoxDecryptKey(item.FirefoxKey4.TempFilename())
171+
logins, err := getFirefoxLoginData()
173172
if err != nil {
174173
return err
175174
}
176-
metaPBE, err := crypto.NewASN1PBE(metaBytes)
177-
if err != nil {
178-
return err
179-
}
180-
181-
k, err := metaPBE.Decrypt(globalSalt, masterKey)
182-
if err != nil {
183-
return err
184-
}
185-
if bytes.Contains(k, []byte("password-check")) {
186-
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
187-
if bytes.Equal(nssA102, keyLin) {
188-
nssPBE, err := crypto.NewASN1PBE(nssA11)
189-
if err != nil {
190-
return err
191-
}
192-
finallyKey, err := nssPBE.Decrypt(globalSalt, masterKey)
193-
if err != nil {
194-
return err
195-
}
196-
197-
finallyKey = finallyKey[:24]
198-
logins, err := getFirefoxLoginData()
199-
if err != nil {
200-
return err
201-
}
202175

203-
for _, v := range logins {
204-
userPBE, err := crypto.NewASN1PBE(v.encryptUser)
205-
if err != nil {
206-
return err
207-
}
208-
pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
209-
if err != nil {
210-
return err
211-
}
212-
user, err := userPBE.Decrypt(finallyKey, masterKey)
213-
if err != nil {
214-
return err
215-
}
216-
pwd, err := pwdPBE.Decrypt(finallyKey, masterKey)
217-
if err != nil {
218-
return err
219-
}
220-
*f = append(*f, loginData{
221-
LoginURL: v.LoginURL,
222-
UserName: string(user),
223-
Password: string(pwd),
224-
CreateDate: v.CreateDate,
225-
})
226-
}
176+
for _, v := range logins {
177+
userPBE, err := crypto.NewASN1PBE(v.encryptUser)
178+
if err != nil {
179+
return err
180+
}
181+
pwdPBE, err := crypto.NewASN1PBE(v.encryptPass)
182+
if err != nil {
183+
return err
184+
}
185+
user, err := userPBE.Decrypt(masterKey)
186+
if err != nil {
187+
return err
188+
}
189+
pwd, err := pwdPBE.Decrypt(masterKey)
190+
if err != nil {
191+
return err
227192
}
193+
*f = append(*f, loginData{
194+
LoginURL: v.LoginURL,
195+
UserName: string(user),
196+
Password: string(pwd),
197+
CreateDate: v.CreateDate,
198+
})
228199
}
200+
229201
sort.Slice(*f, func(i, j int) bool {
230202
return (*f)[i].CreateDate.After((*f)[j].CreateDate)
231203
})
232204
return nil
233205
}
234206

235-
func getFirefoxDecryptKey(key4file string) (item1, item2, a11, a102 []byte, err error) {
236-
keyDB, err := sql.Open("sqlite", key4file)
237-
if err != nil {
238-
return nil, nil, nil, nil, err
239-
}
240-
defer os.Remove(key4file)
241-
defer keyDB.Close()
242-
243-
if err = keyDB.QueryRow(queryMetaData).Scan(&item1, &item2); err != nil {
244-
return nil, nil, nil, nil, err
245-
}
246-
247-
if err = keyDB.QueryRow(queryNssPrivate).Scan(&a11, &a102); err != nil {
248-
return nil, nil, nil, nil, err
249-
}
250-
return item1, item2, a11, a102, nil
251-
}
252-
253207
func getFirefoxLoginData() ([]loginData, error) {
254208
s, err := os.ReadFile(item.FirefoxPassword.TempFilename())
255209
if err != nil {

crypto/crypto.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var (
2020
)
2121

2222
type ASN1PBE interface {
23-
Decrypt(globalSalt, masterPwd []byte) (key []byte, err error)
23+
Decrypt(globalSalt []byte) (key []byte, err error)
2424
}
2525

2626
func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
@@ -60,9 +60,8 @@ type nssPBE struct {
6060
Encrypted []byte
6161
}
6262

63-
func (n nssPBE) Decrypt(globalSalt, masterPwd []byte) (key []byte, err error) {
64-
glmp := append(globalSalt, masterPwd...)
65-
hp := sha1.Sum(glmp)
63+
func (n nssPBE) Decrypt(globalSalt []byte) (key []byte, err error) {
64+
hp := sha1.Sum(globalSalt)
6665
s := append(hp[:], n.salt()...)
6766
chp := sha1.Sum(s)
6867
pes := paddingZero(n.salt(), 20)
@@ -134,7 +133,7 @@ type slatAttr struct {
134133
}
135134
}
136135

137-
func (m metaPBE) Decrypt(globalSalt, _ []byte) (key2 []byte, err error) {
136+
func (m metaPBE) Decrypt(globalSalt []byte) (key2 []byte, err error) {
138137
k := sha1.Sum(globalSalt)
139138
key := pbkdf2.Key(k[:], m.salt(), m.iterationCount(), m.keySize(), sha256.New)
140139
iv := append([]byte{4, 14}, m.iv()...)
@@ -177,7 +176,7 @@ type loginPBE struct {
177176
Encrypted []byte
178177
}
179178

180-
func (l loginPBE) Decrypt(globalSalt, _ []byte) (key []byte, err error) {
179+
func (l loginPBE) Decrypt(globalSalt []byte) (key []byte, err error) {
181180
return des3Decrypt(globalSalt, l.iv(), l.encrypted())
182181
}
183182

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/moond4rk/hackbrowserdata
33
go 1.21
44

55
require (
6+
github.com/DATA-DOG/go-sqlmock v1.5.2
67
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
78
github.com/godbus/dbus/v5 v5.1.0
89
github.com/otiai10/copy v1.14.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
2+
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
13
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
24
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
35
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -23,6 +25,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
2325
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
2426
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
2527
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
28+
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
2629
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
2730
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
2831
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=

0 commit comments

Comments
 (0)