package secret import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procCredRead = modadvapi32.NewProc("CredReadW") procCredFree winproc = modadvapi32.NewProc("CredFree") ) // Interface for syscall.Proc: helps testing type winproc interface { Call(a ...uintptr) (r1, r2 uintptr, lastErr error) } type keyring struct { service string } func keyringProvider(service string) (Provider, error) { return keyring{service}, nil } func (p keyring) GetSecret(key string) (value []byte, err error) { return sysCredRead(p.service+":"+key, sysCRED_TYPE_GENERIC) } // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw type sysCREDENTIAL struct { Flags uint32 Type uint32 TargetName *uint16 Comment *uint16 LastWritten windows.Filetime CredentialBlobSize uint32 CredentialBlob uintptr Persist uint32 AttributeCount uint32 Attributes uintptr TargetAlias *uint16 UserName *uint16 } // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw type sysCRED_TYPE uint32 const ( sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1 sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2 sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3 sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4 sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5 sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6 ) // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw func sysCredRead(targetName string, typ sysCRED_TYPE) ([]byte, error) { var pcred *sysCREDENTIAL targetNamePtr, _ := windows.UTF16PtrFromString(targetName) ret, _, err := syscall.SyscallN( procCredRead.Addr(), uintptr(unsafe.Pointer(targetNamePtr)), // nosemgrep uintptr(typ), 0, uintptr(unsafe.Pointer(&pcred)), // nosemgrep ) if ret == 0 { return nil, err } defer procCredFree.Call(uintptr(unsafe.Pointer(pcred))) // nosemgrep return goBytes(pcred.CredentialBlob, pcred.CredentialBlobSize), nil } // goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`). // This function avoids having cgo as dependency. func goBytes(src uintptr, len uint32) []byte { if src == uintptr(0) { return []byte{} } rv := make([]byte, len) copy(rv, *(*[]byte)(unsafe.Pointer(&struct { // nosemgrep Data unsafe.Pointer Len int Cap int }{ Data: unsafe.Pointer(src), // nosemgrep Len: int(len), Cap: int(len), }))) /* copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ Data: src, Len: int(len), Cap: int(len), }))) */ return rv }