1//go:build !fakekeyring23package keyring45import (6 "context"7 "errors"8 "fmt"9 "unsafe"1011 "golang.org/x/sys/windows"12)1314// System is the Windows Credential Manager, as a generic credential15// named "012:jev-api-key", through advapi32's CredReadW, CredWriteW and16// CredDeleteW.17func System() Store { return credManager{} }1819var (20 advapi32 = windows.NewLazySystemDLL("advapi32.dll")21 credRead = advapi32.NewProc("CredReadW")22 credWrite = advapi32.NewProc("CredWriteW")23 credDelete = advapi32.NewProc("CredDeleteW")24 credFree = advapi32.NewProc("CredFree")25)2627const (28 credTypeGeneric = 129 credPersistLocalMachine = 230 target = Service + ":" + Account31)3233// credential is CREDENTIALW.34type credential struct {35 Flags uint3236 Type uint3237 TargetName *uint1638 Comment *uint1639 LastWritten windows.Filetime40 CredentialBlobSize uint3241 CredentialBlob *byte42 Persist uint3243 AttributeCount uint3244 Attributes uintptr45 TargetAlias *uint1646 UserName *uint1647}4849type credManager struct{}5051func (credManager) Name() string { return "Windows Credential Manager" }5253func (credManager) Get(context.Context) (string, error) {54 name, err := windows.UTF16PtrFromString(target)55 if err != nil {56 return "", err57 }58 var c *credential59 r, _, callErr := credRead.Call(uintptr(unsafe.Pointer(name)), credTypeGeneric, 0, uintptr(unsafe.Pointer(&c)))60 if r == 0 {61 return "", wrap(callErr)62 }63 defer credFree.Call(uintptr(unsafe.Pointer(c)))64 return string(unsafe.Slice(c.CredentialBlob, c.CredentialBlobSize)), nil65}6667func (credManager) Set(_ context.Context, secret string) error {68 if len(secret) == 0 {69 return errors.New("empty key")70 }71 name, err := windows.UTF16PtrFromString(target)72 if err != nil {73 return err74 }75 user, _ := windows.UTF16PtrFromString(Account)76 comment, _ := windows.UTF16PtrFromString(Label)77 blob := []byte(secret)78 c := credential{79 Type: credTypeGeneric, TargetName: name, Comment: comment, UserName: user,80 CredentialBlobSize: uint32(len(blob)), CredentialBlob: &blob[0], Persist: credPersistLocalMachine,81 }82 if r, _, callErr := credWrite.Call(uintptr(unsafe.Pointer(&c)), 0); r == 0 {83 return wrap(callErr)84 }85 return nil86}8788func (credManager) Delete(context.Context) error {89 name, err := windows.UTF16PtrFromString(target)90 if err != nil {91 return err92 }93 if r, _, callErr := credDelete.Call(uintptr(unsafe.Pointer(name)), credTypeGeneric, 0); r == 0 {94 return wrap(callErr)95 }96 return nil97}9899func wrap(err error) error {100 if errors.Is(err, windows.ERROR_NOT_FOUND) {101 return ErrNotFound102 }103 return fmt.Errorf("credential manager: %w", err)104}