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