// SPDX-License-Identifier: MIT //go:build windows package main import ( "fmt" "os" "strings" "syscall" "unicode/utf16" ) const ( enableProcessedInput = 0x0001 enableLineInput = 0x0002 enableEchoInput = 0x0004 ) var procSetConsoleMode = syscall.NewLazyDLL("kernel32.dll").NewProc("SetConsoleMode") func setConsoleMode(h syscall.Handle, mode uint32) error { if r, _, err := procSetConsoleMode.Call(uintptr(h), uintptr(mode)); r == 0 { return err } return nil } // readPassword asks on the console, with typing hidden. It reads the console // as UTF-16 and not as bytes, because bytes from a console are in its code // page, and a password with a letter outside it would derive a different key // than the same password typed in a browser. func readPassword(prompt string) (string, error) { in, err := os.OpenFile("CONIN$", os.O_RDWR, 0) if err != nil { return "", errNoTerminal } defer in.Close() out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0) if err != nil { return "", errNoTerminal } defer out.Close() h := syscall.Handle(in.Fd()) var saved uint32 if err := syscall.GetConsoleMode(h, &saved); err != nil { return "", errNoTerminal } quiet := saved&^enableEchoInput | enableLineInput | enableProcessedInput if err := setConsoleMode(h, quiet); err != nil { return "", errNoTerminal } restore := func() { _ = setConsoleMode(h, saved) } stop := onInterrupt(func() { restore() fmt.Fprintln(out) }) defer stop() defer restore() fmt.Fprint(out, prompt) var got []uint16 buf := make([]uint16, 256) for { var n uint32 if err := syscall.ReadConsole(h, &buf[0], uint32(len(buf)), &n, nil); err != nil { return "", fmt.Errorf("could not read the password: %w", err) } got = append(got, buf[:n]...) if n == 0 || buf[n-1] == '\n' { break } } fmt.Fprintln(out) return strings.TrimRight(string(utf16.Decode(got)), "\r\n"), nil }