//go:build windows package config import ( "fmt" "syscall" "unsafe" ) // Windows DPAPI, reached through crypt32.dll directly rather than pulling in // golang.org/x/sys. Machine scope, matching how the Python side already // protects camera passwords: the agent and the engine may run as different // users on the same PC, and a user-scoped blob written by one cannot be read // by the other. var ( crypt32 = syscall.NewLazyDLL("crypt32.dll") kernel32 = syscall.NewLazyDLL("kernel32.dll") procProtectData = crypt32.NewProc("CryptProtectData") procUnprotectData = crypt32.NewProc("CryptUnprotectData") procLocalFree = kernel32.NewProc("LocalFree") ) const cryptprotectLocalMachine = 0x4 type dataBlob struct { cbData uint32 pbData *byte } func newBlob(d []byte) dataBlob { if len(d) == 0 { return dataBlob{} } return dataBlob{cbData: uint32(len(d)), pbData: &d[0]} } func (b *dataBlob) bytes() []byte { out := make([]byte, b.cbData) copy(out, unsafe.Slice(b.pbData, b.cbData)) return out } func protect(plain []byte) ([]byte, error) { in, out := newBlob(plain), dataBlob{} r, _, err := procProtectData.Call( uintptr(unsafe.Pointer(&in)), 0, 0, 0, 0, cryptprotectLocalMachine, uintptr(unsafe.Pointer(&out))) if r == 0 { return nil, fmt.Errorf("CryptProtectData: %w", err) } defer procLocalFree.Call(uintptr(unsafe.Pointer(out.pbData))) return out.bytes(), nil } func unprotect(blob []byte) ([]byte, error) { in, out := newBlob(blob), dataBlob{} r, _, err := procUnprotectData.Call( uintptr(unsafe.Pointer(&in)), 0, 0, 0, 0, cryptprotectLocalMachine, uintptr(unsafe.Pointer(&out))) if r == 0 { return nil, fmt.Errorf("CryptUnprotectData: %w", err) } defer procLocalFree.Call(uintptr(unsafe.Pointer(out.pbData))) return out.bytes(), nil } func protectionAvailable() bool { return true }