11package registry
22
33import (
4+ "bytes"
45 "context"
56 "errors"
67 "fmt"
@@ -88,6 +89,38 @@ func verifyLoginFlags(flags *pflag.FlagSet, opts loginOptions) error {
8889 return nil
8990}
9091
92+ // readSecretFromStdin reads the secret from r and returns it as a string.
93+ // It trims terminal line-endings (LF, CRLF, or CR), which may be added when
94+ // inputting interactively or piping input. The value is otherwise treated as
95+ // opaque, preserving any other whitespace, including newlines, per [NIST SP 800-63B §5.1.1.2].
96+ // Note that trimming whitespace may still happen elsewhere (see [NIST SP 800-63B (revision 4) §3.1.1.2]);
97+ //
98+ // > Verifiers **MAY** make limited allowances for mistyping (e.g., removing
99+ // > leading and trailing whitespace characters before verification, allowing
100+ // > the verification of passwords with differing cases for the leading character)
101+ //
102+ // [NIST SP 800-63B §5.1.1.2]: https://pages.nist.gov/800-63-3/sp800-63b.html#memsecretver
103+ // [NIST SP 800-63B (revision 4) §3.1.1.2]: https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver
104+ func readSecretFromStdin (r io.Reader ) (string , error ) {
105+ b , err := io .ReadAll (r )
106+ if err != nil {
107+ return "" , err
108+ }
109+ if len (b ) == 0 {
110+ return "" , nil
111+ }
112+
113+ for _ , eol := range [][]byte {[]byte ("\r \n " ), []byte ("\n " ), []byte ("\r " )} {
114+ var ok bool
115+ b , ok = bytes .CutSuffix (b , eol )
116+ if ok {
117+ break
118+ }
119+ }
120+
121+ return string (b ), nil
122+ }
123+
91124func verifyLoginOptions (dockerCLI command.Streams , opts * loginOptions ) error {
92125 if opts .password != "" {
93126 _ , _ = fmt .Fprintln (dockerCLI .Err (), "WARNING! Using --password via the CLI is insecure. Use --password-stdin." )
@@ -97,14 +130,14 @@ func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error {
97130 if opts .user == "" {
98131 return errors .New ("username is empty" )
99132 }
100-
101- contents , err := io .ReadAll (dockerCLI .In ())
133+ p , err := readSecretFromStdin (dockerCLI .In ())
102134 if err != nil {
103135 return err
104136 }
105-
106- opts .password = strings .TrimSuffix (string (contents ), "\n " )
107- opts .password = strings .TrimSuffix (opts .password , "\r " )
137+ if strings .TrimSpace (p ) == "" {
138+ return errors .New ("password is empty" )
139+ }
140+ opts .password = p
108141 }
109142 return nil
110143}
0 commit comments