11package secrets
22
33import (
4+ "bytes"
45 "encoding/json"
56 "errors"
67 "fmt"
@@ -15,8 +16,9 @@ import (
1516)
1617
1718var (
18- errTestKeychain = errors .New ("test -25308 error" )
19- errTestReadBack = errors .New ("test read-back failure" )
19+ errTestDuplicateKeychain = errors .New ("failed to update item in keychain: the specified item already exists in the keychain. (-25299)" )
20+ errTestKeychain = errors .New ("test -25308 error" )
21+ errTestReadBack = errors .New ("test read-back failure" )
2022)
2123
2224func TestKeyringStore_ListDeleteDefault (t * testing.T ) {
@@ -321,6 +323,111 @@ func TestKeyringStoreTokenAccessTokenRoundTrip(t *testing.T) {
321323 }
322324}
323325
326+ func TestKeyringStoreSetTokenRepairsDuplicateAliasWrites (t * testing.T ) {
327+ 328+ subject := "google-sub-123"
329+ expires := time .Date (2026 , 6 , 9 , 16 , 0 , 42 , 0 , time .UTC )
330+
331+ ring := & duplicateOnceKeyring {
332+ ArrayKeyring : keyring .NewArrayKeyring (nil ),
333+ duplicateKeys : map [string ]int {
334+ legacyTokenKey (email ): 1 ,
335+ subjectTokenKey (config .DefaultClientName , subject ): 1 ,
336+ },
337+ removedKeys : map [string ]int {},
338+ }
339+
340+ for _ , key := range []string {
341+ legacyTokenKey (email ),
342+ subjectTokenKey (config .DefaultClientName , subject ),
343+ } {
344+ if err := ring .ArrayKeyring .Set (keyringItem (key , []byte ("stale" ))); err != nil {
345+ t .Fatalf ("seed stale alias %q: %v" , key , err )
346+ }
347+ }
348+
349+ store := & KeyringStore {ring : ring }
350+
351+ err := store .SetToken (config .DefaultClientName , email , Token {
352+ Subject : subject ,
353+ RefreshToken : "rt" ,
354+ AccessToken : "at" ,
355+ AccessTokenExpiresAt : expires ,
356+ })
357+ if err != nil {
358+ t .Fatalf ("SetToken: %v" , err )
359+ }
360+
361+ primary , err := ring .Get (tokenKey (config .DefaultClientName , email ))
362+ if err != nil {
363+ t .Fatalf ("read primary token: %v" , err )
364+ }
365+
366+ for _ , key := range []string {
367+ legacyTokenKey (email ),
368+ subjectTokenKey (config .DefaultClientName , subject ),
369+ } {
370+ item , getErr := ring .Get (key )
371+ if getErr != nil {
372+ t .Fatalf ("expected key %q persisted after duplicate repair: %v" , key , getErr )
373+ }
374+
375+ if ! bytes .Equal (item .Data , primary .Data ) {
376+ t .Fatalf ("alias %q was not replaced with primary payload" , key )
377+ }
378+
379+ if ring .removedKeys [key ] != 1 {
380+ t .Fatalf ("alias %q remove count = %d, want 1" , key , ring .removedKeys [key ])
381+ }
382+ }
383+
384+ got , err := store .GetToken (config .DefaultClientName , email )
385+ if err != nil {
386+ t .Fatalf ("GetToken: %v" , err )
387+ }
388+
389+ if got .AccessToken != "at" || ! got .AccessTokenExpiresAt .Equal (expires ) {
390+ t .Fatalf ("refreshed access metadata was not preserved: %#v" , got )
391+ }
392+ }
393+
394+ func TestKeyringStoreSetTokenKeepsPrimaryDuplicateStrict (t * testing.T ) {
395+ 396+ primaryKey := tokenKey (config .DefaultClientName , email )
397+
398+ ring := & duplicateOnceKeyring {
399+ ArrayKeyring : keyring .NewArrayKeyring (nil ),
400+ duplicateKeys : map [string ]int {
401+ primaryKey : 1 ,
402+ },
403+ removedKeys : map [string ]int {},
404+ }
405+
406+ if err := ring .ArrayKeyring .Set (keyringItem (primaryKey , []byte ("stale-primary" ))); err != nil {
407+ t .Fatalf ("seed stale primary: %v" , err )
408+ }
409+
410+ store := & KeyringStore {ring : ring }
411+
412+ err := store .SetToken (config .DefaultClientName , email , Token {RefreshToken : "rt" })
413+ if err == nil || ! isDuplicateKeyringItemError (err ) {
414+ t .Fatalf ("expected primary duplicate error, got %v" , err )
415+ }
416+
417+ if ring .removedKeys [primaryKey ] != 0 {
418+ t .Fatalf ("primary token was removed during strict write" )
419+ }
420+
421+ item , getErr := ring .Get (primaryKey )
422+ if getErr != nil {
423+ t .Fatalf ("read primary token: %v" , getErr )
424+ }
425+
426+ if string (item .Data ) != "stale-primary" {
427+ t .Fatalf ("primary token changed after failed strict write: %q" , item .Data )
428+ }
429+ }
430+
324431func TestKeyringStoreDeleteTokenAliasPreservesSubjectKey (t * testing.T ) {
325432 ring := keyring .NewArrayKeyring (nil )
326433 store := & KeyringStore {ring : ring }
@@ -494,6 +601,35 @@ func (r *readBackErrorKeyring) Get(_ string) (keyring.Item, error) {
494601}
495602func (r * readBackErrorKeyring ) Keys () ([]string , error ) { return nil , nil }
496603
604+ type duplicateOnceKeyring struct {
605+ * keyring.ArrayKeyring
606+ duplicateKeys map [string ]int
607+ removedKeys map [string ]int
608+ }
609+
610+ func (d * duplicateOnceKeyring ) Set (item keyring.Item ) error {
611+ if remaining := d .duplicateKeys [item .Key ]; remaining > 0 {
612+ d .duplicateKeys [item .Key ] = remaining - 1
613+ return errTestDuplicateKeychain
614+ }
615+
616+ if err := d .ArrayKeyring .Set (item ); err != nil {
617+ return fmt .Errorf ("set array keyring item: %w" , err )
618+ }
619+
620+ return nil
621+ }
622+
623+ func (d * duplicateOnceKeyring ) Remove (key string ) error {
624+ d .removedKeys [key ]++
625+
626+ if err := d .ArrayKeyring .Remove (key ); err != nil {
627+ return fmt .Errorf ("remove array keyring item: %w" , err )
628+ }
629+
630+ return nil
631+ }
632+
497633func TestSetTokenVerifyCatchesReadBackError (t * testing.T ) {
498634 store := & KeyringStore {ring : & readBackErrorKeyring {}}
499635 client := config .DefaultClientName
0 commit comments