@@ -39,7 +39,7 @@ type nssIdentityDTO struct {
3939 IDHex string `json:"idHex"`
4040}
4141
42- func DiscoverNSSStores () []* NSSStore {
42+ func DiscoverNSSStores (ctx context. Context ) []* NSSStore {
4343 var stores []* NSSStore
4444 seen := make (map [string ]struct {})
4545
@@ -49,11 +49,25 @@ func DiscoverNSSStores() []*NSSStore {
4949 }
5050
5151 addStore := func (profileDir , label string ) {
52+ select {
53+ case <- ctx .Done ():
54+ return
55+ default :
56+ }
5257 if profileDir == "" {
5358 return
5459 }
5560 profileDir = filepath .Clean (profileDir )
56- if _ , err := os .Stat (filepath .Join (profileDir , "cert9.db" )); err != nil {
61+ // Accept both modern cert9.db and legacy cert8.db
62+ hasCert9 := func () bool {
63+ _ , err := os .Stat (filepath .Join (profileDir , "cert9.db" ))
64+ return err == nil
65+ }()
66+ hasCert8 := func () bool {
67+ _ , err := os .Stat (filepath .Join (profileDir , "cert8.db" ))
68+ return err == nil
69+ }()
70+ if ! hasCert9 && ! hasCert8 {
5771 return
5872 }
5973 if _ , ok := seen [profileDir ]; ok {
@@ -82,44 +96,57 @@ func DiscoverNSSStores() []*NSSStore {
8296 addStore (profileDir , label )
8397 }
8498
85- // 3. Chromium/Brave NSS DBs (mostly Linux, with optional profile paths on other OSes).
86- var chromiumBases []string
87- switch runtime .GOOS {
88- case "windows" :
89- localAppData := localAppDataDir ()
90- chromiumBases = []string {
91- filepath .Join (localAppData , "Google" , "Chrome" , "User Data" ),
92- filepath .Join (localAppData , "BraveSoftware" , "Brave-Browser" , "User Data" ),
93- filepath .Join (localAppData , "Chromium" , "User Data" ),
94- }
95- case "darwin" :
96- chromiumBases = []string {
97- filepath .Join (home , "Library" , "Application Support" , "Google" , "Chrome" ),
98- filepath .Join (home , "Library" , "Application Support" , "BraveSoftware" , "Brave-Browser" ),
99- filepath .Join (home , "Library" , "Application Support" , "Chromium" ),
100- }
101- default :
102- chromiumBases = []string {
103- filepath .Join (home , ".config" , "google-chrome" ),
104- filepath .Join (home , ".config" , "BraveSoftware" , "Brave-Browser" ),
105- filepath .Join (home , ".config" , "chromium" ),
106- filepath .Join (home , "snap" , "brave" , "common" , ".pki" , "nssdb" ),
107- }
108- snapPaths , _ := filepath .Glob (filepath .Join (home , "snap" , "*" , "current" , ".pki" , "nssdb" ))
109- chromiumBases = append (chromiumBases , snapPaths ... )
110- }
111- for _ , base := range chromiumBases {
99+ // 3. Chromium-family NSS DBs — covers Chrome, Brave, Edge, Opera, Vivaldi, etc.
100+ for _ , base := range chromiumBaseDirs () {
112101 addStore (base , "Browser NSS" )
113-
114102 entries , _ := os .ReadDir (base )
115103 for _ , entry := range entries {
116- if entry .IsDir () && (entry .Name () == "Default" || strings .HasPrefix (entry .Name (), "Profile " )) {
117- profileDir := filepath .Join (base , entry .Name ())
118- addStore (profileDir , "Browser Profile: " + entry .Name ())
104+ if ! entry .IsDir () {
105+ continue
106+ }
107+ n := entry .Name ()
108+ if n == "Default" || strings .HasPrefix (n , "Profile " ) || strings .HasPrefix (n , "Guest Profile" ) {
109+ addStore (filepath .Join (base , n ), "Browser Profile: " + n )
119110 }
120111 }
121112 }
122113
114+ // 4. System-wide NSS locations commonly used on Linux
115+ if runtime .GOOS != "windows" {
116+ for _ , sysPath := range []string {"/etc/pki/nssdb" , "/etc/nssdb" } {
117+ if _ , err := os .Stat (filepath .Join (sysPath , "cert9.db" )); err == nil {
118+ addStore (sysPath , "System NSS" )
119+ }
120+ }
121+ }
122+
123+ // 5. Aggressive walk: look for cert9.db/cert8.db under all likely roots.
124+ walkRoots := uniqueExistingDirs (
125+ filepath .Join (home , ".pki" ),
126+ filepath .Join (home , ".mozilla" ),
127+ filepath .Join (home , ".thunderbird" ),
128+ filepath .Join (home , ".librewolf" ),
129+ filepath .Join (home , ".waterfox" ),
130+ filepath .Join (home , ".var" , "app" ), // flatpak user data
131+ filepath .Join (home , "snap" ), // snap user data
132+ filepath .Join (home , ".local" , "share" ),
133+ localAppDataDir (),
134+ appDataDir (),
135+ )
136+ if runtime .GOOS == "darwin" {
137+ walkRoots = append (walkRoots , filepath .Join (home , "Library" , "Application Support" ))
138+ }
139+ if runtime .GOOS == "linux" {
140+ walkRoots = append (walkRoots ,
141+ "/etc/pki" ,
142+ "/etc/ssl" ,
143+ )
144+ }
145+ candidates := walkNSSCandidates (ctx , walkRoots , 7 , 500 )
146+ for _ , dir := range candidates {
147+ addStore (dir , "Browser NSS" )
148+ }
149+
123150 return stores
124151}
125152
@@ -168,10 +195,33 @@ func findNSSLib() string {
168195 }
169196 default :
170197 paths := []string {
198+ // Debian/Ubuntu multiarch
171199 "/usr/lib/x86_64-linux-gnu/libsoftokn3.so" ,
200+ "/usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so" ,
201+ "/usr/lib/i386-linux-gnu/libsoftokn3.so" ,
202+ "/usr/lib/aarch64-linux-gnu/libsoftokn3.so" ,
203+ "/usr/lib/arm-linux-gnueabihf/libsoftokn3.so" ,
204+ // Generic / Fedora / RHEL / Arch
172205 "/usr/lib/libsoftokn3.so" ,
173206 "/usr/lib64/libsoftokn3.so" ,
174- "/usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so" ,
207+ "/usr/lib64/nss/libsoftokn3.so" ,
208+ // libnss3 fallback (some distros only ship this)
209+ "/usr/lib/x86_64-linux-gnu/libnss3.so" ,
210+ "/usr/lib/libnss3.so" ,
211+ "/usr/lib64/libnss3.so" ,
212+ // Firefox snap bundle
213+ "/snap/firefox/current/usr/lib/firefox/libsoftokn3.so" ,
214+ "/snap/firefox/current/usr/lib/firefox/libnss3.so" ,
215+ // Firefox flatpak bundle
216+ "/var/lib/flatpak/app/org.mozilla.firefox/current/active/files/lib/firefox/libsoftokn3.so" ,
217+ "/var/lib/flatpak/app/org.mozilla.firefox/current/active/files/lib/firefox/libnss3.so" ,
218+ }
219+ // Also check user-local flatpak
220+ if home , err := os .UserHomeDir (); err == nil {
221+ paths = append (paths ,
222+ filepath .Join (home , ".local" , "share" , "flatpak" , "app" , "org.mozilla.firefox" , "current" , "active" , "files" , "lib" , "firefox" , "libsoftokn3.so" ),
223+ filepath .Join (home , ".local" , "share" , "flatpak" , "app" , "org.mozilla.firefox" , "current" , "active" , "files" , "lib" , "firefox" , "libnss3.so" ),
224+ )
175225 }
176226 for _ , p := range paths {
177227 if _ , err := os .Stat (p ); err == nil {
@@ -182,6 +232,79 @@ func findNSSLib() string {
182232 return ""
183233}
184234
235+ func uniqueExistingDirs (dirs ... string ) []string {
236+ seen := make (map [string ]struct {})
237+ var out []string
238+ for _ , d := range dirs {
239+ if d == "" {
240+ continue
241+ }
242+ d = filepath .Clean (d )
243+ if _ , err := os .Stat (d ); err != nil {
244+ continue
245+ }
246+ if _ , ok := seen [d ]; ok {
247+ continue
248+ }
249+ seen [d ] = struct {}{}
250+ out = append (out , d )
251+ }
252+ return out
253+ }
254+
255+ func walkNSSCandidates (ctx context.Context , roots []string , maxDepth int , limit int ) []string {
256+ type void struct {}
257+ seen := make (map [string ]void )
258+ var results []string
259+ for _ , root := range roots {
260+ select {
261+ case <- ctx .Done ():
262+ return results
263+ default :
264+ }
265+ root = filepath .Clean (root )
266+ rootDepth := len (strings .Split (root , string (os .PathSeparator )))
267+ _ = filepath .WalkDir (root , func (path string , d os.DirEntry , err error ) error {
268+ if err != nil {
269+ return nil
270+ }
271+ select {
272+ case <- ctx .Done ():
273+ return context .Canceled
274+ default :
275+ }
276+ depth := len (strings .Split (path , string (os .PathSeparator ))) - rootDepth
277+ if depth > maxDepth {
278+ if d .IsDir () {
279+ return filepath .SkipDir
280+ }
281+ return nil
282+ }
283+ if d .IsDir () {
284+ return nil
285+ }
286+ name := d .Name ()
287+ if name != "cert9.db" && name != "cert8.db" {
288+ return nil
289+ }
290+ dir := filepath .Dir (path )
291+ if _ , ok := seen [dir ]; ok {
292+ return nil
293+ }
294+ seen [dir ] = void {}
295+ results = append (results , dir )
296+ if limit > 0 && len (results ) >= limit {
297+ return context .Canceled
298+ }
299+ return nil
300+ })
301+ if limit > 0 && len (results ) >= limit {
302+ break
303+ }
304+ }
305+ return results
306+ }
307+
185308func (s * NSSStore ) List (ctx context.Context ) ([]pkcs12store.Identity , error ) {
186309 return s .listViaWorker (ctx )
187310}
0 commit comments