[crypto] ssh: fix support for partial success authentication responses in client

223 views
Skip to first unread message

Gobot Gobot (Gerrit)

unread,
Jan 17, 2018, 8:01:17 AM1/17/18
to Sami Pönkänen, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Congratulations on opening your first change. Thank you for your contribution!

Next steps:
Within the next week or so, a maintainer will review your change and provide
feedback. See https://golang.org/doc/contribute.html#review for more info and
tips to get your patch through code review.

Most changes in the Go project go through a few rounds of revision. This can be
surprising to people new to the project. The careful, iterative review process
is our way of helping mentor contributors and ensuring that their contributions
have a lasting impact.

During May-July and Nov-Jan the Go project is in a code freeze, during which
little code gets reviewed or merged. If a reviewer responds with a comment like
R=go1.11, it means that this CL will be reviewed as part of the next development
cycle. See https://golang.org/s/release for more details.

View Change

    To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

    Gerrit-Project: crypto
    Gerrit-Branch: master
    Gerrit-MessageType: comment
    Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
    Gerrit-Change-Number: 88035
    Gerrit-PatchSet: 1
    Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
    Gerrit-CC: Gobot Gobot <go...@golang.org>
    Gerrit-Comment-Date: Wed, 17 Jan 2018 13:01:15 +0000
    Gerrit-HasComments: No
    Gerrit-HasLabels: No

    Sami Pönkänen (Gerrit)

    unread,
    Jan 17, 2018, 8:24:41 AM1/17/18
    to Ian Lance Taylor, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

    Sami Pönkänen has uploaded this change for review.

    View Change

    ssh: fix support for partial success authentication responses in client

    The existing client side authentication does not handle correctly
    the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
    responses.

    This commit fixes two problems:
    1) RetryableAuthMethod() now breaks out from the retry loop and
    returns when underlying auth method fails with partial success
    set to true.
    2) Book keeping of tried (and failed) auth methods in
    clientAuthenticate() does not mark an auth method failed if it
    fails with partial success set to true.

    Fixes golang/go#23461

    Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
    ---
    M ssh/client_auth.go
    M ssh/keys_test.go
    2 files changed, 67 insertions(+), 52 deletions(-)

    diff --git a/ssh/client_auth.go b/ssh/client_auth.go
    index a1252cb..5f44b77 100644
    --- a/ssh/client_auth.go
    +++ b/ssh/client_auth.go
    @@ -11,6 +11,14 @@
    "io"
    )

    +type authResult int
    +
    +const (
    + authFailure authResult = iota
    + authPartialSuccess
    + authSuccess
    +)
    +
    // clientAuthenticate authenticates with the remote server. See RFC 4252.
    func (c *connection) clientAuthenticate(config *ClientConfig) error {
    // initiate user auth session
    @@ -37,11 +45,12 @@
    if err != nil {
    return err
    }
    - if ok {
    + if ok == authSuccess {
    // success
    return nil
    + } else if ok == authFailure {
    + tried[auth.method()] = true
    }
    - tried[auth.method()] = true
    if methods == nil {
    methods = lastMethods
    }
    @@ -82,7 +91,7 @@
    // If authentication is not successful, a []string of alternative
    // method names is returned. If the slice is nil, it will be ignored
    // and the previous set of possible methods will be reused.
    - auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
    + auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)

    // method returns the RFC 4252 method name.
    method() string
    @@ -91,13 +100,13 @@
    // "none" authentication, RFC 4252 section 5.2.
    type noneAuth int

    -func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
    +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
    if err := c.writePacket(Marshal(&userAuthRequestMsg{
    User: user,
    Service: serviceSSH,
    Method: "none",
    })); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    return handleAuthResponse(c)
    @@ -111,7 +120,7 @@
    // a function call, e.g. by prompting the user.
    type passwordCallback func() (password string, err error)

    -func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
    +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
    type passwordAuthMsg struct {
    User string `sshtype:"50"`
    Service string
    @@ -125,7 +134,7 @@
    // The program may only find out that the user doesn't have a password
    // when prompting.
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    if err := c.writePacket(Marshal(&passwordAuthMsg{
    @@ -135,7 +144,7 @@
    Reply: false,
    Password: pw,
    })); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    return handleAuthResponse(c)
    @@ -178,7 +187,7 @@
    return "publickey"
    }

    -func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
    +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
    // Authentication is performed by sending an enquiry to test if a key is
    // acceptable to the remote. If the key is acceptable, the client will
    // attempt to authenticate with the valid key. If not the client will repeat
    @@ -186,13 +195,13 @@

    signers, err := cb()
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    var methods []string
    for _, signer := range signers {
    ok, err := validateKey(signer.PublicKey(), user, c)
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    if !ok {
    continue
    @@ -206,7 +215,7 @@
    Method: cb.method(),
    }, []byte(pub.Type()), pubKey))
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    // manually wrap the serialized signature in a string
    @@ -224,24 +233,24 @@
    }
    p := Marshal(&msg)
    if err := c.writePacket(p); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    - var success bool
    + var success authResult
    success, methods, err = handleAuthResponse(c)
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    // If authentication succeeds or the list of available methods does not
    // contain the "publickey" method, do not attempt to authenticate with any
    // other keys. According to RFC 4252 Section 7, the latter can occur when
    // additional authentication methods are required.
    - if success || !containsMethod(methods, cb.method()) {
    + if success == authSuccess || !containsMethod(methods, cb.method()) {
    return success, methods, err
    }
    }

    - return false, methods, nil
    + return authFailure, methods, nil
    }

    func containsMethod(methods []string, method string) bool {
    @@ -318,28 +327,31 @@
    // handleAuthResponse returns whether the preceding authentication request succeeded
    // along with a list of remaining authentication methods to try next and
    // an error if an unexpected response was received.
    -func handleAuthResponse(c packetConn) (bool, []string, error) {
    +func handleAuthResponse(c packetConn) (authResult, []string, error) {
    for {
    packet, err := c.readPacket()
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    switch packet[0] {
    case msgUserAuthBanner:
    if err := handleBannerResponse(c, packet); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    case msgUserAuthFailure:
    var msg userAuthFailureMsg
    if err := Unmarshal(packet, &msg); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    - return false, msg.Methods, nil
    + if msg.PartialSuccess {
    + return authPartialSuccess, msg.Methods, nil
    + }
    + return authFailure, msg.Methods, nil
    case msgUserAuthSuccess:
    - return true, nil, nil
    + return authSuccess, nil, nil
    default:
    - return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
    + return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
    }
    }
    }
    @@ -381,7 +393,7 @@
    return "keyboard-interactive"
    }

    -func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
    +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
    type initiateMsg struct {
    User string `sshtype:"50"`
    Service string
    @@ -395,20 +407,20 @@
    Service: serviceSSH,
    Method: "keyboard-interactive",
    })); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    for {
    packet, err := c.readPacket()
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    // like handleAuthResponse, but with less options.
    switch packet[0] {
    case msgUserAuthBanner:
    if err := handleBannerResponse(c, packet); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    continue
    case msgUserAuthInfoRequest:
    @@ -416,18 +428,21 @@
    case msgUserAuthFailure:
    var msg userAuthFailureMsg
    if err := Unmarshal(packet, &msg); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    - return false, msg.Methods, nil
    + if msg.PartialSuccess {
    + return authPartialSuccess, msg.Methods, nil
    + }
    + return authFailure, msg.Methods, nil
    case msgUserAuthSuccess:
    - return true, nil, nil
    + return authSuccess, nil, nil
    default:
    - return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
    + return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
    }

    var msg userAuthInfoRequestMsg
    if err := Unmarshal(packet, &msg); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    // Manually unpack the prompt/echo pairs.
    @@ -437,7 +452,7 @@
    for i := 0; i < int(msg.NumPrompts); i++ {
    prompt, r, ok := parseString(rest)
    if !ok || len(r) == 0 {
    - return false, nil, errors.New("ssh: prompt format error")
    + return authFailure, nil, errors.New("ssh: prompt format error")
    }
    prompts = append(prompts, string(prompt))
    echos = append(echos, r[0] != 0)
    @@ -445,16 +460,16 @@
    }

    if len(rest) != 0 {
    - return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
    + return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
    }

    answers, err := cb(msg.User, msg.Instruction, prompts, echos)
    if err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }

    if len(answers) != len(prompts) {
    - return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
    + return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
    }
    responseLength := 1 + 4
    for _, a := range answers {
    @@ -470,7 +485,7 @@
    }

    if err := c.writePacket(serialized); err != nil {
    - return false, nil, err
    + return authFailure, nil, err
    }
    }
    }
    @@ -480,10 +495,10 @@
    maxTries int
    }

    -func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
    +func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
    for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
    ok, methods, err = r.authMethod.auth(session, user, c, rand)
    - if ok || err != nil { // either success or error terminate
    + if ok != authFailure || err != nil { // either success, partial success or error terminate
    return ok, methods, err
    }
    }
    diff --git a/ssh/keys_test.go b/ssh/keys_test.go
    index 20ab954..9a90abc 100644
    --- a/ssh/keys_test.go
    +++ b/ssh/keys_test.go
    @@ -234,7 +234,7 @@
    }
    }

    -type authResult struct {
    +type testAuthResult struct {
    pubKey PublicKey
    options []string
    comments string
    @@ -242,11 +242,11 @@
    ok bool
    }

    -func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
    +func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) {
    rest := authKeys
    - var values []authResult
    + var values []testAuthResult
    for len(rest) > 0 {
    - var r authResult
    + var r testAuthResult
    var err error
    r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
    r.ok = (err == nil)
    @@ -264,7 +264,7 @@
    pub, pubSerialized := getTestKey()
    line := "ssh-rsa " + pubSerialized + " user@host"
    testAuthorizedKeys(t, []byte(line),
    - []authResult{
    + []testAuthResult{
    {pub, nil, "user@host", "", true},
    })
    }
    @@ -286,7 +286,7 @@
    authOptions := strings.Join(authWithOptions, eol)
    rest2 := strings.Join(authWithOptions[3:], eol)
    rest3 := strings.Join(authWithOptions[6:], eol)
    - testAuthorizedKeys(t, []byte(authOptions), []authResult{
    + testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{
    {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
    {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
    {nil, nil, "", "", false},
    @@ -297,7 +297,7 @@
    func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
    pub, pubSerialized := getTestKey()
    authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
    - testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
    + testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{
    {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
    })
    }
    @@ -305,7 +305,7 @@
    func TestAuthWithQuotedCommaInEnv(t *testing.T) {
    pub, pubSerialized := getTestKey()
    authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
    - testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
    + testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{
    {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
    })
    }
    @@ -314,11 +314,11 @@
    pub, pubSerialized := getTestKey()
    authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
    authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
    - testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
    + testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{
    {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
    })

    - testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
    + testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{
    {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
    })
    }
    @@ -327,7 +327,7 @@
    _, pubSerialized := getTestKey()
    authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
    #more to follow but still no valid keys`)
    - testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
    + testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{
    {nil, nil, "", "", false},
    })
    }
    @@ -337,7 +337,7 @@
    authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
    env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)

    - testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
    + testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{
    {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
    })
    }

    To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

    Gerrit-Project: crypto
    Gerrit-Branch: master
    Gerrit-MessageType: newchange

    Han-Wen Nienhuys (Gerrit)

    unread,
    Jan 22, 2018, 5:29:30 AM1/22/18
    to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

    Is there a way to trigger this condition with an openssh configuration? If so, I'd really like to see such a configuration being tested under test/

    View Change

      To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-Project: crypto
      Gerrit-Branch: master
      Gerrit-MessageType: comment
      Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
      Gerrit-Change-Number: 88035
      Gerrit-PatchSet: 1
      Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
      Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
      Gerrit-CC: Gobot Gobot <go...@golang.org>
      Gerrit-Comment-Date: Mon, 22 Jan 2018 10:29:26 +0000
      Gerrit-HasComments: No
      Gerrit-HasLabels: No

      Sami Pönkänen (Gerrit)

      unread,
      Jan 22, 2018, 5:37:53 AM1/22/18
      to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

      Patch Set 1:

      Is there a way to trigger this condition with an openssh configuration? If so, I'd really like to see such a configuration being tested under test/

      Yes, see https://github.com/golang/go/issues/23461 for details, but in a nut shell use RetryableAuthMethod(PasswordCallback, 2) and put this to /etc/ssh/sshd_config:
      AuthenticationMethods password,publickey

      View Change

        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

        Gerrit-Project: crypto
        Gerrit-Branch: master
        Gerrit-MessageType: comment
        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
        Gerrit-Change-Number: 88035
        Gerrit-PatchSet: 1
        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
        Gerrit-CC: Gobot Gobot <go...@golang.org>
        Gerrit-Comment-Date: Mon, 22 Jan 2018 10:37:50 +0000
        Gerrit-HasComments: No
        Gerrit-HasLabels: No

        Sami Pönkänen (Gerrit)

        unread,
        Jan 22, 2018, 7:02:12 AM1/22/18
        to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

        Patch Set 1:

        Patch Set 1:

        Is there a way to trigger this condition with an openssh configuration? If so, I'd really like to see such a configuration being tested under test/

        Yes, see https://github.com/golang/go/issues/23461 for details, but in a nut shell use RetryableAuthMethod(PasswordCallback, 2) and put this to /etc/ssh/sshd_config:
        AuthenticationMethods password,publickey

        And below is the output of 'ssh -v' to an OpenSSH server with the above config:

        $ ssh -v bu...@192.168.200.101
        OpenSSH_7.2p2 Ubuntu-4ubuntu2.2, OpenSSL 1.0.2g 1 Mar 2016
        debug1: Reading configuration data /etc/ssh/ssh_config
        debug1: /etc/ssh/ssh_config line 19: Applying options for *
        debug1: Connecting to 192.168.200.101 [192.168.200.101] port 22.
        debug1: Connection established.
        debug1: identity file /home/ext-sponkane/.ssh/id_rsa type 1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_rsa-cert type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_dsa type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_dsa-cert type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_ecdsa type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_ecdsa-cert type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_ed25519 type -1
        debug1: key_load_public: No such file or directory
        debug1: identity file /home/ext-sponkane/.ssh/id_ed25519-cert type -1
        debug1: Enabling compatibility mode for protocol 2.0
        debug1: Local version string SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2
        debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
        debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
        debug1: Authenticating to 192.168.200.101:22 as 'burt'
        debug1: SSH2_MSG_KEXINIT sent
        debug1: SSH2_MSG_KEXINIT received
        debug1: kex: algorithm: curve255...@libssh.org
        debug1: kex: host key algorithm: ecdsa-sha2-nistp256
        debug1: kex: server->client cipher: chacha20...@openssh.com MAC: <implicit> compression: none
        debug1: kex: client->server cipher: chacha20...@openssh.com MAC: <implicit> compression: none
        debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
        debug1: Server host key: ecdsa-sha2-nistp256 SHA256:czx7JiH9M++oXzx4UlbQzI5pEkgilfF8gdsGiZ3Gps4
        debug1: Host '192.168.200.101' is known and matches the ECDSA host key.
        debug1: Found key in /home/ext-sponkane/.ssh/known_hosts:2
        debug1: rekey after 134217728 blocks
        debug1: SSH2_MSG_NEWKEYS sent
        debug1: expecting SSH2_MSG_NEWKEYS
        debug1: rekey after 134217728 blocks
        debug1: SSH2_MSG_NEWKEYS received
        debug1: SSH2_MSG_EXT_INFO received
        debug1: kex_input_ext_info: server-sig-algs=<rsa-sha2-256,rsa-sha2-512>
        debug1: SSH2_MSG_SERVICE_ACCEPT received
        debug1: Authentications that can continue: password
        debug1: Next authentication method: password
        bu...@192.168.200.101's password:
        Authenticated with partial success.
        debug1: Authentications that can continue: publickey
        debug1: Next authentication method: publickey
        debug1: Trying private key: /home/ext-sponkane/.ssh/id_rsa
        Enter passphrase for key '/home/ext-sponkane/.ssh/id_rsa':
        debug1: Authentication succeeded (publickey).
        Authenticated to 192.168.200.101 ([192.168.200.101]:22).
        debug1: channel 0: new [client-session]
        debug1: Requesting no-more-...@openssh.com
        debug1: Entering interactive session.
        debug1: pledge: network
        debug1: client_input_global_request: rtype hostk...@openssh.com want_reply 0
        debug1: Sending environment.
        debug1: Sending env LC_PAPER = fi_FI.UTF-8
        debug1: Sending env LC_ADDRESS = fi_FI.UTF-8
        debug1: Sending env LC_MONETARY = fi_FI.UTF-8
        debug1: Sending env LC_NUMERIC = fi_FI.UTF-8
        debug1: Sending env LC_TELEPHONE = fi_FI.UTF-8
        debug1: Sending env LC_IDENTIFICATION = fi_FI.UTF-8
        debug1: Sending env LANG = en_US.UTF-8
        debug1: Sending env LC_MEASUREMENT = fi_FI.UTF-8
        debug1: Sending env LC_TIME = fi_FI.UTF-8
        debug1: Sending env LC_NAME = fi_FI.UTF-8

        View Change

          To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-Project: crypto
          Gerrit-Branch: master
          Gerrit-MessageType: comment
          Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
          Gerrit-Change-Number: 88035
          Gerrit-PatchSet: 1
          Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
          Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
          Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
          Gerrit-CC: Gobot Gobot <go...@golang.org>
          Gerrit-Comment-Date: Mon, 22 Jan 2018 12:02:08 +0000
          Gerrit-HasComments: No
          Gerrit-HasLabels: No

          Han-Wen Nienhuys (Gerrit)

          unread,
          Jan 22, 2018, 7:03:44 AM1/22/18
          to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

          nice. Can you create a testcase for this? There is code to run a Go client against openssh's sshd under test/

          View Change

            To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: crypto
            Gerrit-Branch: master
            Gerrit-MessageType: comment
            Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
            Gerrit-Change-Number: 88035
            Gerrit-PatchSet: 1
            Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
            Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
            Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
            Gerrit-CC: Gobot Gobot <go...@golang.org>
            Gerrit-Comment-Date: Mon, 22 Jan 2018 12:03:40 +0000
            Gerrit-HasComments: No
            Gerrit-HasLabels: No

            Sami Pönkänen (Gerrit)

            unread,
            Jan 23, 2018, 9:10:03 AM1/23/18
            to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

            Sami Pönkänen uploaded patch set #2 to this change.

            View Change

            ssh: fix support for partial success authentication responses in client

            The existing client side authentication does not handle correctly
            the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
            responses.

            This commit fixes two problems:
            1) RetryableAuthMethod() now breaks out from the retry loop and
            returns when underlying auth method fails with partial success
            set to true.
            2) Book keeping of tried (and failed) auth methods in
            clientAuthenticate() does not mark an auth method failed if it
            fails with partial success set to true.

            Fixes golang/go#23461

            Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
            ---
            M ssh/client_auth.go
            M ssh/keys_test.go
            A ssh/test/multi_auth_test.go
            M ssh/test/test_unix_test.go
            4 files changed, 319 insertions(+), 59 deletions(-)

            To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: crypto
            Gerrit-Branch: master
            Gerrit-MessageType: newpatchset
            Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
            Gerrit-Change-Number: 88035
            Gerrit-PatchSet: 2

            Han-Wen Nienhuys (Gerrit)

            unread,
            Jan 23, 2018, 9:24:00 AM1/23/18
            to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

            thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

            View Change

            3 comments:

            • File ssh/test/multi_auth_test.go:

              • Patch Set #2, Line 11: these tests must be run as root.

                that is a shame, but better than nothing. I'll double check with damien if there is no way around this.

              • Patch Set #2, Line 38: multiAuthPassword

                this is the password for a live user on the system, right? If so, drop this.

                The command-line is publicly visible through ps -ef.

              • Patch Set #2, Line 83: numKeyboardInteractiveCallbacks

                is it necessary for these to be global? If not, can you encapsulate this in struct that each test creates from scratch?

            To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: crypto
            Gerrit-Branch: master
            Gerrit-MessageType: comment
            Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
            Gerrit-Change-Number: 88035
            Gerrit-PatchSet: 2
            Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
            Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
            Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
            Gerrit-CC: Gobot Gobot <go...@golang.org>
            Gerrit-Comment-Date: Tue, 23 Jan 2018 14:23:56 +0000
            Gerrit-HasComments: Yes
            Gerrit-HasLabels: No

            Sami Pönkänen (Gerrit)

            unread,
            Jan 23, 2018, 9:26:52 AM1/23/18
            to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

            Patch Set 1:

            nice. Can you create a testcase for this? There is code to run a Go client against openssh's sshd under test/

            Hi,

            I had some trouble creating proper test cases for this bug, but I managed to create tests for:
            1) "AuthenticationMethods password,publickey" with RetryableAuthMethod(Password)
            2) "AuthenticationMethods keyboard-interactive,publickey" with RetryableAuthMethod(KeyboardInteractiveChallenge)
            3) "AuthenticationMethods publickey,password" with RetryableAuthMethod(Password)
            4) "AuthenticationMethods publickey,keyboard-interactive" with RetryableAuthMethod(KeyboardInteractiveChallenge)

            Without patch set 1 test 1 fails as expected, and tests 3 and 4 pass as expected.

            Also test 2 passes - although the underlying problem is there - because there is no way to detect the actual bug outside of clientAuthenticate() and retryableAuthMethod.auth(). It is visible in the OpenSSH server logs though; look for these lines repeating:
            debug1: userauth-request for user sami service ssh-connection method keyboard-interactive [preauth]
            debug1: attempt 2 failures 0 [preauth]
            debug2: Unrecognized authentication method name: keyboard-interactive [preauth]

            Another problem with the tests was that the OpenSSH server validates passwords using PAM, and therefore the tests must be run as root/sudo and with real username/password input data. Because of this restriction the multi-auth tests are skipped unless the tests are run with command line argument '-user'. The password for the tests can be given with command line argument '-password' or otherwise it will be prompted interactively while running the tests.

            So, not perfect but better than no tests, right?

            Let me know if there is a more elegant way of implementing password/keyboard-interactive tests with OpenSSH server.

            Sami

            View Change

              To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-Project: crypto
              Gerrit-Branch: master
              Gerrit-MessageType: comment
              Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
              Gerrit-Change-Number: 88035
              Gerrit-PatchSet: 2
              Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
              Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
              Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
              Gerrit-CC: Gobot Gobot <go...@golang.org>
              Gerrit-Comment-Date: Tue, 23 Jan 2018 14:26:49 +0000
              Gerrit-HasComments: No
              Gerrit-HasLabels: No

              Sami Pönkänen (Gerrit)

              unread,
              Jan 24, 2018, 4:17:29 AM1/24/18
              to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

              Patch Set 2:

              (3 comments)

              thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

              Line 11: Yes, agree. Let me know if you know a way to work around this.

              Line 38: Ok. Do you need another way to define password (besides prompted password)? Thinking of making test automation easier. Maybe os.Getenv()?

              Line 83: Ok, I can move these to per test case context. After that change there would still be the global var for the prompted password so that it needs to be prompted only once per test run and reused in following test cases.

              View Change

                To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-Project: crypto
                Gerrit-Branch: master
                Gerrit-MessageType: comment
                Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                Gerrit-Change-Number: 88035
                Gerrit-PatchSet: 2
                Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                Gerrit-CC: Gobot Gobot <go...@golang.org>
                Gerrit-Comment-Date: Wed, 24 Jan 2018 09:17:24 +0000
                Gerrit-HasComments: No
                Gerrit-HasLabels: No

                Sami Pönkänen (Gerrit)

                unread,
                Jan 25, 2018, 5:26:15 AM1/25/18
                to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

                Patch Set 2:

                Patch Set 2:

                (3 comments)

                thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

                Line 11: Yes, agree. Let me know if you know a way to work around this.

                Line 38: Ok. Do you need another way to define password (besides prompted password)? Thinking of making test automation easier. Maybe os.Getenv()?

                Line 83: Ok, I can move these to per test case context. After that change there would still be the global var for the prompted password so that it needs to be prompted only once per test run and reused in following test cases.

                As per your suggestion I have created a sshd_test_pw.so wrapper library for injecting test password data for sshd + PAM.

                I am planning to add the source code to test/lib/linux/src/sshd_test_pw.so and the compiled library to test/lib/linux/amd64/sshd_test_pw.so. Are these the correct places for them?

                View Change

                  To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                  Gerrit-Project: crypto
                  Gerrit-Branch: master
                  Gerrit-MessageType: comment
                  Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                  Gerrit-Change-Number: 88035
                  Gerrit-PatchSet: 2
                  Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                  Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                  Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                  Gerrit-CC: Gobot Gobot <go...@golang.org>
                  Gerrit-Comment-Date: Thu, 25 Jan 2018 10:26:11 +0000
                  Gerrit-HasComments: No
                  Gerrit-HasLabels: No

                  Han-Wen Nienhuys (Gerrit)

                  unread,
                  Jan 25, 2018, 5:32:36 AM1/25/18
                  to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                  Patch Set 2:

                  Patch Set 2:

                  Patch Set 2:

                  (3 comments)

                  thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

                  Line 11: Yes, agree. Let me know if you know a way to work around this.

                  Line 38: Ok. Do you need another way to define password (besides prompted password)? Thinking of making test automation easier. Maybe os.Getenv()?

                  Line 83: Ok, I can move these to per test case context. After that change there would still be the global var for the prompted password so that it needs to be prompted only once per test run and reused in following test cases.

                  As per your suggestion I have created a sshd_test_pw.so wrapper library for injecting test password data for sshd + PAM.

                  I am planning to add the source code to test/lib/linux/src/sshd_test_pw.so and the compiled library to test/lib/linux/amd64/sshd_test_pw.so. Are these the correct places for them?

                  I suggest to simply put it in the same directory, and to leave out the binary artifact. That will require people to have a C compiler, but that's less of requirement than that they use AMD64.

                  View Change

                    To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                    Gerrit-Project: crypto
                    Gerrit-Branch: master
                    Gerrit-MessageType: comment
                    Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                    Gerrit-Change-Number: 88035
                    Gerrit-PatchSet: 2
                    Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                    Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                    Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                    Gerrit-CC: Gobot Gobot <go...@golang.org>
                    Gerrit-Comment-Date: Thu, 25 Jan 2018 10:32:33 +0000
                    Gerrit-HasComments: No
                    Gerrit-HasLabels: No

                    Sami Pönkänen (Gerrit)

                    unread,
                    Jan 25, 2018, 5:40:25 AM1/25/18
                    to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

                    Patch Set 2:

                    Patch Set 2:

                    Patch Set 2:

                    Patch Set 2:

                    (3 comments)

                    thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

                    Line 11: Yes, agree. Let me know if you know a way to work around this.

                    Line 38: Ok. Do you need another way to define password (besides prompted password)? Thinking of making test automation easier. Maybe os.Getenv()?

                    Line 83: Ok, I can move these to per test case context. After that change there would still be the global var for the prompted password so that it needs to be prompted only once per test run and reused in following test cases.

                    As per your suggestion I have created a sshd_test_pw.so wrapper library for injecting test password data for sshd + PAM.

                    I am planning to add the source code to test/lib/linux/src/sshd_test_pw.so and the compiled library to test/lib/linux/amd64/sshd_test_pw.so. Are these the correct places for them?

                    I suggest to simply put it in the same directory, and to leave out the binary artifact. That will require people to have a C compiler, but that's less of requirement than that they use AMD64.

                    Placing the C src file in test/ is a bit problematic:
                    $ go fmt
                    can't load package: package golang.org/x/crypto/ssh/test: C source files not allowed when not using cgo or SWIG: sshd_test_pw.c

                    Would subdirectory sshd_test_pw/ be better?

                    View Change

                      To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                      Gerrit-Project: crypto
                      Gerrit-Branch: master
                      Gerrit-MessageType: comment
                      Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                      Gerrit-Change-Number: 88035
                      Gerrit-PatchSet: 2
                      Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                      Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                      Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                      Gerrit-CC: Gobot Gobot <go...@golang.org>
                      Gerrit-Comment-Date: Thu, 25 Jan 2018 10:40:21 +0000
                      Gerrit-HasComments: No
                      Gerrit-HasLabels: No

                      Sami Pönkänen (Gerrit)

                      unread,
                      Jan 25, 2018, 6:26:11 AM1/25/18
                      to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

                      Patch Set 2:

                      Patch Set 2:

                      Patch Set 2:

                      Patch Set 2:

                      Patch Set 2:

                      (3 comments)

                      thanks. This is just a quick look. I'll look more closely tomorrow. Hopefully I'll get something back from djm too.

                      Line 11: Yes, agree. Let me know if you know a way to work around this.

                      Line 38: Ok. Do you need another way to define password (besides prompted password)? Thinking of making test automation easier. Maybe os.Getenv()?

                      Line 83: Ok, I can move these to per test case context. After that change there would still be the global var for the prompted password so that it needs to be prompted only once per test run and reused in following test cases.

                      As per your suggestion I have created a sshd_test_pw.so wrapper library for injecting test password data for sshd + PAM.

                      I am planning to add the source code to test/lib/linux/src/sshd_test_pw.so and the compiled library to test/lib/linux/amd64/sshd_test_pw.so. Are these the correct places for them?

                      I suggest to simply put it in the same directory, and to leave out the binary artifact. That will require people to have a C compiler, but that's less of requirement than that they use AMD64.

                      Placing the C src file in test/ is a bit problematic:
                      $ go fmt
                      can't load package: package golang.org/x/crypto/ssh/test: C source files not allowed when not using cgo or SWIG: sshd_test_pw.c

                      Would subdirectory sshd_test_pw/ be better?

                      Please ignore; adding '// +build ignore' solves the issue.

                      View Change

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 2
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Thu, 25 Jan 2018 11:26:07 +0000
                        Gerrit-HasComments: No
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Jan 25, 2018, 6:38:07 AM1/25/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #3 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 628 insertions(+), 59 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 3

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Jan 25, 2018, 8:08:22 AM1/25/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        (+ damien)

                        View Change

                        4 comments:

                        • File ssh/test/sshd_test_pw.c:

                          • Patch Set #3, Line 25: _GNU_SOURCE

                            this looks considerably more complicate than the poc we got from Damien; why? Is this because of BSD vs Linux?

                          • Patch Set #3, Line 87:

                            abort if the env vars aren't defined.

                          • Patch Set #3, Line 147: real_getpwnam_r

                            why? This never happens for our test?

                          • Patch Set #3, Line 151: strncpy

                            can you use

                              strdup()

                            instead? That will get rid of the hardcoded lengths and \0 meddling.

                            also, I don't understand why you copy to pw_name from pwd->pw_name, to copy it into pwd->pw_name down below again.

                            also, why get the passwd from the real function anyway? You could copy strings from the environment?

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 3
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Thu, 25 Jan 2018 13:08:18 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Jan 26, 2018, 5:10:24 AM1/26/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #4 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 530 insertions(+), 59 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 4

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Jan 30, 2018, 11:33:32 AM1/30/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        sorry for the delay. This week had some unexpected things pop up.

                        View Change

                        3 comments:

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 4
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Tue, 30 Jan 2018 16:33:27 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Jan 31, 2018, 2:59:11 AM1/31/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #5 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 500 insertions(+), 57 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 5

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Jan 31, 2018, 8:38:47 AM1/31/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        View Change

                        15 comments:

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 5
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Wed, 31 Jan 2018 13:38:43 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Feb 1, 2018, 11:29:47 AM2/1/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #6 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 442 insertions(+), 57 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 6

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Feb 5, 2018, 3:54:44 AM2/5/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        View Change

                        5 comments:

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 6
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Mon, 05 Feb 2018 08:54:39 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Feb 5, 2018, 9:04:19 AM2/5/18
                        to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

                        Apparently I have not saved my draft comments before, so here's the old ones too.

                        Sami

                        View Change

                        27 comments:

                          • since you only call this on construction, can you make […]

                            Yes, makes sense.

                          • Patch Set #5, Line 41: Fatalf("Failed t

                            t. […]

                            Done

                          • can return nil here, I think.

                            Yes, looks like KeyboardInteractiveChallenge.auth() handles nil when returning err != nil.

                          • Patch Set #5, Line 80: hTestCtx(t, "key

                            you should be able to cast ctx.passwordCallback to the function type directly, […]

                            Done

                          • shouldn't this assert that the login attempt as successful? here and below.

                          • server.Dial() calls t.Fatalf() if ssh.NewClientConn() returns error, so auth failures are already checked there. Do you want an explicit check here?

                          • why is it necessary to use RetryableAuthMethod ? […]

                            The bug in client_auth.go appears (I think) only if RetryableAuthMethod() is used. And in real world it is used in most cases where user is prompted for password, right?

                          • Done

                          • nit: pwcbs is a bit too abbreviated. […]

                            Ok, changed to numPasswordCbs

                          • this and below is part of the test case specification, and should not be in your mutable context cla […]

                            Ok, split test parameters from multiAuthTestCtx to multiAuthTestCase.

                          • Done

                          • if you store this as []string{"password","publickey"} in the test case specification, you can easily […]

                            Yes, makes sense.

                        • File ssh/test/sshd_test_pw.c:

                          • this looks considerably more complicate than the poc we got from Damien; why? Is this because of BSD […]

                            Yeah, maybe not using strdup() makes the code more messy. It is simpler now.

                            As to why implement getpwnam_r() and getspnam_r()? On linux PAM calls both getpwnam() and getpwnam_r(). I do not see any calls to getspnam() or getspnam_r(), but decided to implement then anyway as PAM source code has references to them.

                          • Patch Set #3, Line 87: return 1;

                          • abort if the env vars aren't defined.

                          • why? This never happens for our test?

                          • Why, as in why call real_getpwnam_r(), or why to implement getpwnam_r()?

                            Answer to the latter is that PAM needs it on my ubuntu 16.04.

                          • * Pointers to real functions in libc */
                            static struct passwd * (*real_getpwnam)(const char *) = NULL;
                            st

                          • since we generate the test password, is there a reason put a non-zero salt here?

                          • Reason is just trying to do things the way they should be done, although I guess in this test program it would not be mandatory to do so.

                            Ok, I'll replace this with a zero salt.

                          • can just use + , I think.

                          • Done

                          • Done

                          • Done

                          • did you forget to post the "Done" comments?

                            Done

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 6
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Mon, 05 Feb 2018 14:04:13 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Sami Pönkänen (Gerrit)

                        unread,
                        Feb 5, 2018, 9:06:08 AM2/5/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #7 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 450 insertions(+), 57 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 7

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Feb 5, 2018, 9:37:04 AM2/5/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        there are some stylistic nits, but this looks mostly OK.

                        Unfortunately, I can't make this work locally. Let me try this on my home machine which runs a different linux version.

                        Patch set 7:Run-TryBot +1Code-Review +1

                        View Change

                        1 comment:

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 7
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Mon, 05 Feb 2018 14:37:00 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: Yes

                        Sami Pönkänen (Gerrit)

                        unread,
                        Feb 5, 2018, 9:43:45 AM2/5/18
                        to Han-Wen Nienhuys, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Sami Pönkänen uploaded patch set #8 to this change.

                        View Change

                        ssh: fix support for partial success authentication responses in client

                        The existing client side authentication does not handle correctly
                        the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                        responses.

                        This commit fixes two problems in ssh library:

                        1) RetryableAuthMethod() now breaks out from the retry loop and
                        returns when underlying auth method fails with partial success
                        set to true.
                        2) Book keeping of tried (and failed) auth methods in
                        clientAuthenticate() does not mark an auth method failed if it
                        fails with partial success set to true.

                        Fixes golang/go#23461

                        Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        ---
                        M ssh/client_auth.go
                        M ssh/keys_test.go
                        A ssh/test/multi_auth_test.go
                        A ssh/test/sshd_test_pw.c
                        M ssh/test/test_unix_test.go
                        5 files changed, 450 insertions(+), 57 deletions(-)

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: newpatchset
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 8

                        Sami Pönkänen (Gerrit)

                        unread,
                        Feb 5, 2018, 9:44:30 AM2/5/18
                        to goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, Gobot Gobot, golang-co...@googlegroups.com

                        Done

                        View Change

                        1 comment:

                        To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: crypto
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                        Gerrit-Change-Number: 88035
                        Gerrit-PatchSet: 8
                        Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                        Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                        Gerrit-CC: Gobot Gobot <go...@golang.org>
                        Gerrit-Comment-Date: Mon, 05 Feb 2018 14:44:25 +0000
                        Gerrit-HasComments: Yes
                        Gerrit-HasLabels: No

                        Han-Wen Nienhuys (Gerrit)

                        unread,
                        Feb 8, 2018, 5:39:06 AM2/8/18
                        to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                        Patch set 8:Run-TryBot +1Code-Review +2

                        View Change

                          To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                          Gerrit-Project: crypto
                          Gerrit-Branch: master
                          Gerrit-MessageType: comment
                          Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                          Gerrit-Change-Number: 88035
                          Gerrit-PatchSet: 8
                          Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                          Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                          Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                          Gerrit-CC: Gobot Gobot <go...@golang.org>
                          Gerrit-Comment-Date: Thu, 08 Feb 2018 10:39:02 +0000
                          Gerrit-HasComments: No
                          Gerrit-HasLabels: Yes

                          Gobot Gobot (Gerrit)

                          unread,
                          Feb 8, 2018, 5:39:12 AM2/8/18
                          to Sami Pönkänen, goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, golang-co...@googlegroups.com

                          TryBots beginning. Status page: https://farmer.golang.org/try?commit=ac9bf65e

                          View Change

                            To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                            Gerrit-Project: crypto
                            Gerrit-Branch: master
                            Gerrit-MessageType: comment
                            Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                            Gerrit-Change-Number: 88035
                            Gerrit-PatchSet: 8
                            Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                            Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                            Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                            Gerrit-CC: Gobot Gobot <go...@golang.org>
                            Gerrit-Comment-Date: Thu, 08 Feb 2018 10:39:08 +0000
                            Gerrit-HasComments: No
                            Gerrit-HasLabels: No

                            Han-Wen Nienhuys (Gerrit)

                            unread,
                            Feb 8, 2018, 5:39:23 AM2/8/18
                            to Sami Pönkänen, goph...@pubsubhelper.golang.org, Gobot Gobot, golang-co...@googlegroups.com

                            Tested sshd test on Fedora 26.

                            View Change

                              To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                              Gerrit-Project: crypto
                              Gerrit-Branch: master
                              Gerrit-MessageType: comment
                              Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                              Gerrit-Change-Number: 88035
                              Gerrit-PatchSet: 8
                              Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                              Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                              Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                              Gerrit-CC: Gobot Gobot <go...@golang.org>
                              Gerrit-Comment-Date: Thu, 08 Feb 2018 10:39:21 +0000
                              Gerrit-HasComments: No
                              Gerrit-HasLabels: No

                              Gobot Gobot (Gerrit)

                              unread,
                              Feb 8, 2018, 5:43:06 AM2/8/18
                              to Sami Pönkänen, goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, golang-co...@googlegroups.com

                              Build is still in progress...
                              This change failed on windows-amd64-2016:
                              See https://storage.googleapis.com/go-build-log/104445e3/windows-amd64-2016_c0bb6414.log

                              Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.

                              View Change

                                To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                                Gerrit-Project: crypto
                                Gerrit-Branch: master
                                Gerrit-MessageType: comment
                                Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                                Gerrit-Change-Number: 88035
                                Gerrit-PatchSet: 8
                                Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                                Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                                Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                                Gerrit-CC: Gobot Gobot <go...@golang.org>
                                Gerrit-Comment-Date: Thu, 08 Feb 2018 10:43:02 +0000
                                Gerrit-HasComments: No
                                Gerrit-HasLabels: No

                                Gobot Gobot (Gerrit)

                                unread,
                                Feb 8, 2018, 5:43:46 AM2/8/18
                                to Sami Pönkänen, goph...@pubsubhelper.golang.org, Han-Wen Nienhuys, golang-co...@googlegroups.com

                                2 of 7 TryBots failed:
                                Failed on windows-amd64-2016: https://storage.googleapis.com/go-build-log/104445e3/windows-amd64-2016_c0bb6414.log
                                Failed on windows-386-2008: https://storage.googleapis.com/go-build-log/104445e3/windows-386-2008_f42d1606.log

                                Consult https://build.golang.org/ to see whether they are new failures.

                                Patch set 8:TryBot-Result -1

                                View Change

                                  To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                                  Gerrit-Project: crypto
                                  Gerrit-Branch: master
                                  Gerrit-MessageType: comment
                                  Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                                  Gerrit-Change-Number: 88035
                                  Gerrit-PatchSet: 8
                                  Gerrit-Owner: Sami Pönkänen <sami.p...@gmail.com>
                                  Gerrit-Reviewer: Gobot Gobot <go...@golang.org>
                                  Gerrit-Reviewer: Han-Wen Nienhuys <han...@google.com>
                                  Gerrit-Reviewer: Sami Pönkänen <sami.p...@gmail.com>
                                  Gerrit-Comment-Date: Thu, 08 Feb 2018 10:43:43 +0000
                                  Gerrit-HasComments: No
                                  Gerrit-HasLabels: Yes

                                  Han-Wen Nienhuys (Gerrit)

                                  unread,
                                  Feb 8, 2018, 10:07:06 AM2/8/18
                                  to Sami Pönkänen, goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Gobot Gobot, golang-co...@googlegroups.com

                                  Han-Wen Nienhuys merged this change.

                                  View Change

                                  Approvals: Han-Wen Nienhuys: Looks good to me, approved; Run TryBots Objections: Gobot Gobot: TryBots failed
                                  ssh: fix support for partial success authentication responses in client

                                  The existing client side authentication does not handle correctly
                                  the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication
                                  responses.

                                  This commit fixes two problems in ssh library:
                                  1) RetryableAuthMethod() now breaks out from the retry loop and
                                  returns when underlying auth method fails with partial success
                                  set to true.
                                  2) Book keeping of tried (and failed) auth methods in
                                  clientAuthenticate() does not mark an auth method failed if it
                                  fails with partial success set to true.

                                  Fixes golang/go#23461

                                  Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                                  Reviewed-on: https://go-review.googlesource.com/88035
                                  Reviewed-by: Han-Wen Nienhuys <han...@google.com>
                                  Run-TryBot: Han-Wen Nienhuys <han...@google.com>

                                  ---
                                  M ssh/client_auth.go
                                  M ssh/keys_test.go
                                  A ssh/test/multi_auth_test.go
                                  A ssh/test/sshd_test_pw.c
                                  M ssh/test/test_unix_test.go
                                  5 files changed, 450 insertions(+), 57 deletions(-)

                                  diff --git a/ssh/client_auth.go b/ssh/client_auth.go
                                  index a1252cb..5f44b77 100644
                                  --- a/ssh/client_auth.go
                                  +++ b/ssh/client_auth.go
                                  @@ -11,6 +11,14 @@
                                  "io"
                                  )

                                  +type authResult int
                                  +
                                  +const (
                                  + authFailure authResult = iota
                                  + authPartialSuccess
                                  + authSuccess
                                  +)
                                  +
                                  // clientAuthenticate authenticates with the remote server. See RFC 4252.
                                  func (c *connection) clientAuthenticate(config *ClientConfig) error {
                                  // initiate user auth session
                                  @@ -37,11 +45,12 @@
                                  if err != nil {
                                  return err
                                  }
                                  - if ok {
                                  + if ok == authSuccess {
                                  // success
                                  return nil
                                  + } else if ok == authFailure {
                                  + tried[auth.method()] = true
                                  }
                                  - tried[auth.method()] = true
                                  if methods == nil {
                                  methods = lastMethods
                                  }
                                  @@ -82,7 +91,7 @@
                                  // If authentication is not successful, a []string of alternative
                                  // method names is returned. If the slice is nil, it will be ignored
                                  // and the previous set of possible methods will be reused.
                                  - auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
                                  + auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)

                                  // method returns the RFC 4252 method name.
                                  method() string
                                  @@ -91,13 +100,13 @@
                                  // "none" authentication, RFC 4252 section 5.2.
                                  type noneAuth int

                                  -func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
                                  +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
                                  if err := c.writePacket(Marshal(&userAuthRequestMsg{
                                  User: user,
                                  Service: serviceSSH,
                                  Method: "none",
                                  })); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  return handleAuthResponse(c)
                                  @@ -111,7 +120,7 @@
                                  // a function call, e.g. by prompting the user.
                                  type passwordCallback func() (password string, err error)

                                  -func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
                                  +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
                                  type passwordAuthMsg struct {
                                  User string `sshtype:"50"`
                                  Service string
                                  @@ -125,7 +134,7 @@
                                  // The program may only find out that the user doesn't have a password
                                  // when prompting.
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  if err := c.writePacket(Marshal(&passwordAuthMsg{
                                  @@ -135,7 +144,7 @@
                                  Reply: false,
                                  Password: pw,
                                  })); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  return handleAuthResponse(c)
                                  @@ -178,7 +187,7 @@
                                  return "publickey"
                                  }

                                  -func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
                                  +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
                                  // Authentication is performed by sending an enquiry to test if a key is
                                  // acceptable to the remote. If the key is acceptable, the client will
                                  // attempt to authenticate with the valid key. If not the client will repeat
                                  @@ -186,13 +195,13 @@

                                  signers, err := cb()
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  var methods []string
                                  for _, signer := range signers {
                                  ok, err := validateKey(signer.PublicKey(), user, c)
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  if !ok {
                                  continue
                                  @@ -206,7 +215,7 @@
                                  Method: cb.method(),
                                  }, []byte(pub.Type()), pubKey))
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  // manually wrap the serialized signature in a string
                                  @@ -224,24 +233,24 @@
                                  }
                                  p := Marshal(&msg)
                                  if err := c.writePacket(p); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  - var success bool
                                  + var success authResult
                                  success, methods, err = handleAuthResponse(c)
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  // If authentication succeeds or the list of available methods does not
                                  // contain the "publickey" method, do not attempt to authenticate with any
                                  // other keys. According to RFC 4252 Section 7, the latter can occur when
                                  // additional authentication methods are required.
                                  - if success || !containsMethod(methods, cb.method()) {
                                  + if success == authSuccess || !containsMethod(methods, cb.method()) {
                                  return success, methods, err
                                  }
                                  }

                                  - return false, methods, nil
                                  + return authFailure, methods, nil
                                  }

                                  func containsMethod(methods []string, method string) bool {
                                  @@ -318,28 +327,31 @@
                                  // handleAuthResponse returns whether the preceding authentication request succeeded
                                  // along with a list of remaining authentication methods to try next and
                                  // an error if an unexpected response was received.
                                  -func handleAuthResponse(c packetConn) (bool, []string, error) {
                                  +func handleAuthResponse(c packetConn) (authResult, []string, error) {
                                  for {
                                  packet, err := c.readPacket()
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  switch packet[0] {
                                  case msgUserAuthBanner:
                                  if err := handleBannerResponse(c, packet); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  case msgUserAuthFailure:
                                  var msg userAuthFailureMsg
                                  if err := Unmarshal(packet, &msg); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  - return false, msg.Methods, nil
                                  + if msg.PartialSuccess {
                                  + return authPartialSuccess, msg.Methods, nil
                                  + }
                                  + return authFailure, msg.Methods, nil
                                  case msgUserAuthSuccess:
                                  - return true, nil, nil
                                  + return authSuccess, nil, nil
                                  default:
                                  - return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
                                  + return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
                                  }
                                  }
                                  }
                                  @@ -381,7 +393,7 @@
                                  return "keyboard-interactive"
                                  }

                                  -func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
                                  +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
                                  type initiateMsg struct {
                                  User string `sshtype:"50"`
                                  Service string
                                  @@ -395,20 +407,20 @@
                                  Service: serviceSSH,
                                  Method: "keyboard-interactive",
                                  })); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  for {
                                  packet, err := c.readPacket()
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  // like handleAuthResponse, but with less options.
                                  switch packet[0] {
                                  case msgUserAuthBanner:
                                  if err := handleBannerResponse(c, packet); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  continue
                                  case msgUserAuthInfoRequest:
                                  @@ -416,18 +428,21 @@
                                  case msgUserAuthFailure:
                                  var msg userAuthFailureMsg
                                  if err := Unmarshal(packet, &msg); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  - return false, msg.Methods, nil
                                  + if msg.PartialSuccess {
                                  + return authPartialSuccess, msg.Methods, nil
                                  + }
                                  + return authFailure, msg.Methods, nil
                                  case msgUserAuthSuccess:
                                  - return true, nil, nil
                                  + return authSuccess, nil, nil
                                  default:
                                  - return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
                                  + return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
                                  }

                                  var msg userAuthInfoRequestMsg
                                  if err := Unmarshal(packet, &msg); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  // Manually unpack the prompt/echo pairs.
                                  @@ -437,7 +452,7 @@
                                  for i := 0; i < int(msg.NumPrompts); i++ {
                                  prompt, r, ok := parseString(rest)
                                  if !ok || len(r) == 0 {
                                  - return false, nil, errors.New("ssh: prompt format error")
                                  + return authFailure, nil, errors.New("ssh: prompt format error")
                                  }
                                  prompts = append(prompts, string(prompt))
                                  echos = append(echos, r[0] != 0)
                                  @@ -445,16 +460,16 @@
                                  }

                                  if len(rest) != 0 {
                                  - return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
                                  + return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
                                  }

                                  answers, err := cb(msg.User, msg.Instruction, prompts, echos)
                                  if err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }

                                  if len(answers) != len(prompts) {
                                  - return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
                                  + return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
                                  }
                                  responseLength := 1 + 4
                                  for _, a := range answers {
                                  @@ -470,7 +485,7 @@
                                  }

                                  if err := c.writePacket(serialized); err != nil {
                                  - return false, nil, err
                                  + return authFailure, nil, err
                                  }
                                  }
                                  }
                                  @@ -480,10 +495,10 @@
                                  maxTries int
                                  }

                                  -func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
                                  +func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
                                  for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
                                  ok, methods, err = r.authMethod.auth(session, user, c, rand)
                                  - if ok || err != nil { // either success or error terminate
                                  + if ok != authFailure || err != nil { // either success, partial success or error terminate
                                  return ok, methods, err
                                  }
                                  }
                                  diff --git a/ssh/keys_test.go b/ssh/keys_test.go
                                  index 20ab954..9a90abc 100644
                                  --- a/ssh/keys_test.go
                                  +++ b/ssh/keys_test.go
                                  @@ -234,7 +234,7 @@
                                  }
                                  }

                                  -type authResult struct {
                                  +type testAuthResult struct {
                                  pubKey PublicKey
                                  options []string
                                  comments string
                                  @@ -242,11 +242,11 @@
                                  ok bool
                                  }

                                  -func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
                                  +func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) {
                                  rest := authKeys
                                  - var values []authResult
                                  + var values []testAuthResult
                                  for len(rest) > 0 {
                                  - var r authResult
                                  + var r testAuthResult
                                  var err error
                                  r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
                                  r.ok = (err == nil)
                                  @@ -264,7 +264,7 @@
                                  pub, pubSerialized := getTestKey()
                                  line := "ssh-rsa " + pubSerialized + " user@host"
                                  testAuthorizedKeys(t, []byte(line),
                                  - []authResult{
                                  + []testAuthResult{
                                  {pub, nil, "user@host", "", true},
                                  })
                                  }
                                  @@ -286,7 +286,7 @@
                                  authOptions := strings.Join(authWithOptions, eol)
                                  rest2 := strings.Join(authWithOptions[3:], eol)
                                  rest3 := strings.Join(authWithOptions[6:], eol)
                                  - testAuthorizedKeys(t, []byte(authOptions), []authResult{
                                  + testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{
                                  {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
                                  {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
                                  {nil, nil, "", "", false},
                                  @@ -297,7 +297,7 @@
                                  func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
                                  pub, pubSerialized := getTestKey()
                                  authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
                                  - testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{
                                  {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
                                  })
                                  }
                                  @@ -305,7 +305,7 @@
                                  func TestAuthWithQuotedCommaInEnv(t *testing.T) {
                                  pub, pubSerialized := getTestKey()
                                  authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
                                  - testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{
                                  {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
                                  })
                                  }
                                  @@ -314,11 +314,11 @@
                                  pub, pubSerialized := getTestKey()
                                  authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
                                  authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
                                  - testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{
                                  {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
                                  })

                                  - testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{
                                  {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
                                  })
                                  }
                                  @@ -327,7 +327,7 @@
                                  _, pubSerialized := getTestKey()
                                  authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
                                  #more to follow but still no valid keys`)
                                  - testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{
                                  {nil, nil, "", "", false},
                                  })
                                  }
                                  @@ -337,7 +337,7 @@
                                  authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
                                  env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)

                                  - testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
                                  + testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{
                                  {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
                                  })
                                  }
                                  diff --git a/ssh/test/multi_auth_test.go b/ssh/test/multi_auth_test.go
                                  new file mode 100644
                                  index 0000000..16fb1f6
                                  --- /dev/null
                                  +++ b/ssh/test/multi_auth_test.go
                                  @@ -0,0 +1,142 @@
                                  +// Copyright 2017 The Go Authors. All rights reserved.
                                  +// Use of this source code is governed by a BSD-style
                                  +// license that can be found in the LICENSE file.
                                  +
                                  +// Tests for ssh client multi-auth
                                  +//
                                  +// These tests run a simple go ssh client against OpenSSH server
                                  +// over unix domain sockets. The tests use multiple combinations
                                  +// of password, keyboard-interactive and publickey authentication
                                  +// methods.
                                  +//
                                  +// A wrapper library for making sshd PAM authentication use test
                                  +// passwords is required in ./sshd_test_pw.so. If the library does
                                  +// not exist these tests will be skipped. See compile instructions
                                  +// (for linux) in file ./sshd_test_pw.c.
                                  +
                                  +package test
                                  +
                                  +import (
                                  + "fmt"
                                  + "strings"
                                  + "testing"
                                  +
                                  + "golang.org/x/crypto/ssh"
                                  +)
                                  +
                                  +// test cases
                                  +type multiAuthTestCase struct {
                                  + authMethods []string
                                  + expectedPasswordCbs int
                                  + expectedKbdIntCbs int
                                  +}
                                  +
                                  +// test context
                                  +type multiAuthTestCtx struct {
                                  + password string
                                  + numPasswordCbs int
                                  + numKbdIntCbs int
                                  +}
                                  +
                                  +// create test context
                                  +func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
                                  + password, err := randomPassword()
                                  + if err != nil {
                                  + t.Fatalf("Failed to generate random test password: %s", err.Error())
                                  + }
                                  +
                                  + return &multiAuthTestCtx{
                                  + password: password,
                                  + }
                                  +}
                                  +
                                  +// password callback
                                  +func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
                                  + ctx.numPasswordCbs++
                                  + return ctx.password, nil
                                  +}
                                  +
                                  +// keyboard-interactive callback
                                  +func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
                                  + if len(questions) == 0 {
                                  + return nil, nil
                                  + }
                                  +
                                  + ctx.numKbdIntCbs++
                                  + if len(questions) == 1 {
                                  + return []string{ctx.password}, nil
                                  + }
                                  +
                                  + return nil, fmt.Errorf("unsupported keyboard-interactive flow")
                                  +}
                                  +
                                  +// TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
                                  +func TestMultiAuth(t *testing.T) {
                                  + testCases := []multiAuthTestCase{
                                  + // Test password,publickey authentication, assert that password callback is called 1 time
                                  + multiAuthTestCase{
                                  + authMethods: []string{"password", "publickey"},
                                  + expectedPasswordCbs: 1,
                                  + },
                                  + // Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
                                  + multiAuthTestCase{
                                  + authMethods: []string{"keyboard-interactive", "publickey"},
                                  + expectedKbdIntCbs: 1,
                                  + },
                                  + // Test publickey,password authentication, assert that password callback is called 1 time
                                  + multiAuthTestCase{
                                  + authMethods: []string{"publickey", "password"},
                                  + expectedPasswordCbs: 1,
                                  + },
                                  + // Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
                                  + multiAuthTestCase{
                                  + authMethods: []string{"publickey", "keyboard-interactive"},
                                  + expectedKbdIntCbs: 1,
                                  + },
                                  + // Test password,password authentication, assert that password callback is called 2 times
                                  + multiAuthTestCase{
                                  + authMethods: []string{"password", "password"},
                                  + expectedPasswordCbs: 2,
                                  + },
                                  + }
                                  +
                                  + for _, testCase := range testCases {
                                  + t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
                                  + ctx := newMultiAuthTestCtx(t)
                                  +
                                  + server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
                                  + defer server.Shutdown()
                                  +
                                  + clientConfig := clientConfig()
                                  + server.setTestPassword(clientConfig.User, ctx.password)
                                  +
                                  + publicKeyAuthMethod := clientConfig.Auth[0]
                                  + clientConfig.Auth = nil
                                  + for _, authMethod := range testCase.authMethods {
                                  + switch authMethod {
                                  + case "publickey":
                                  + clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
                                  + case "password":
                                  + clientConfig.Auth = append(clientConfig.Auth,
                                  + ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
                                  + case "keyboard-interactive":
                                  + clientConfig.Auth = append(clientConfig.Auth,
                                  + ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
                                  + default:
                                  + t.Fatalf("Unknown authentication method %s", authMethod)
                                  + }
                                  + }
                                  +
                                  + conn := server.Dial(clientConfig)
                                  + defer conn.Close()
                                  +
                                  + if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
                                  + t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
                                  + }
                                  +
                                  + if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
                                  + t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
                                  + }
                                  + })
                                  + }
                                  +}
                                  diff --git a/ssh/test/sshd_test_pw.c b/ssh/test/sshd_test_pw.c
                                  new file mode 100644
                                  index 0000000..2794a56
                                  --- /dev/null
                                  +++ b/ssh/test/sshd_test_pw.c
                                  @@ -0,0 +1,173 @@
                                  +// Copyright 2017 The Go Authors. All rights reserved.
                                  +// Use of this source code is governed by a BSD-style
                                  +// license that can be found in the LICENSE file.
                                  +
                                  +// sshd_test_pw.c
                                  +// Wrapper to inject test password data for sshd PAM authentication
                                  +//
                                  +// This wrapper implements custom versions of getpwnam, getpwnam_r,
                                  +// getspnam and getspnam_r. These functions first call their real
                                  +// libc versions, then check if the requested user matches test user
                                  +// specified in env variable TEST_USER and if so replace the password
                                  +// with crypted() value of TEST_PASSWD env variable.
                                  +//
                                  +// Compile:
                                  +// gcc -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
                                  +//
                                  +// Compile with debug:
                                  +// gcc -DVERBOSE -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
                                  +//
                                  +// Run sshd:
                                  +// LD_PRELOAD="sshd_test_pw.so" TEST_USER="..." TEST_PASSWD="..." sshd ...
                                  +
                                  +// +build ignore
                                  +
                                  +#define _GNU_SOURCE
                                  +#include <string.h>
                                  +#include <pwd.h>
                                  +#include <shadow.h>
                                  +#include <dlfcn.h>
                                  +#include <stdlib.h>
                                  +#include <unistd.h>
                                  +#include <stdio.h>
                                  +
                                  +#ifdef VERBOSE
                                  +#define DEBUG(X...) fprintf(stderr, X)
                                  +#else
                                  +#define DEBUG(X...) while (0) { }
                                  +#endif
                                  +
                                  +/* crypt() password */
                                  +static char *
                                  +pwhash(char *passwd) {
                                  + return strdup(crypt(passwd, "$6$"));
                                  +}
                                  +
                                  +/* Pointers to real functions in libc */
                                  +static struct passwd * (*real_getpwnam)(const char *) = NULL;
                                  +static int (*real_getpwnam_r)(const char *, struct passwd *, char *, size_t, struct passwd **) = NULL;
                                  +static struct spwd * (*real_getspnam)(const char *) = NULL;
                                  +static int (*real_getspnam_r)(const char *, struct spwd *, char *, size_t, struct spwd **) = NULL;
                                  +
                                  +/* Cached test user and test password */
                                  +static char *test_user = NULL;
                                  +static char *test_passwd_hash = NULL;
                                  +
                                  +static void
                                  +init(void) {
                                  + /* Fetch real libc function pointers */
                                  + real_getpwnam = dlsym(RTLD_NEXT, "getpwnam");
                                  + real_getpwnam_r = dlsym(RTLD_NEXT, "getpwnam_r");
                                  + real_getspnam = dlsym(RTLD_NEXT, "getspnam");
                                  + real_getspnam_r = dlsym(RTLD_NEXT, "getspnam_r");
                                  +
                                  + /* abort if env variables are not defined */
                                  + if (getenv("TEST_USER") == NULL || getenv("TEST_PASSWD") == NULL) {
                                  + fprintf(stderr, "env variables TEST_USER and TEST_PASSWD are missing\n");
                                  + abort();
                                  + }
                                  +
                                  + /* Fetch test user and test password from env */
                                  + test_user = strdup(getenv("TEST_USER"));
                                  + test_passwd_hash = pwhash(getenv("TEST_PASSWD"));
                                  +
                                  + DEBUG("sshd_test_pw init():\n");
                                  + DEBUG("\treal_getpwnam: %p\n", real_getpwnam);
                                  + DEBUG("\treal_getpwnam_r: %p\n", real_getpwnam_r);
                                  + DEBUG("\treal_getspnam: %p\n", real_getspnam);
                                  + DEBUG("\treal_getspnam_r: %p\n", real_getspnam_r);
                                  + DEBUG("\tTEST_USER: '%s'\n", test_user);
                                  + DEBUG("\tTEST_PASSWD: '%s'\n", getenv("TEST_PASSWD"));
                                  + DEBUG("\tTEST_PASSWD_HASH: '%s'\n", test_passwd_hash);
                                  +}
                                  +
                                  +static int
                                  +is_test_user(const char *name) {
                                  + if (test_user != NULL && strcmp(test_user, name) == 0)
                                  + return 1;
                                  + return 0;
                                  +}
                                  +
                                  +/* getpwnam */
                                  +
                                  +struct passwd *
                                  +getpwnam(const char *name) {
                                  + struct passwd *pw;
                                  +
                                  + DEBUG("sshd_test_pw getpwnam(%s)\n", name);
                                  +
                                  + if (real_getpwnam == NULL)
                                  + init();
                                  + if ((pw = real_getpwnam(name)) == NULL)
                                  + return NULL;
                                  +
                                  + if (is_test_user(name))
                                  + pw->pw_passwd = strdup(test_passwd_hash);
                                  +
                                  + return pw;
                                  +}
                                  +
                                  +/* getpwnam_r */
                                  +
                                  +int
                                  +getpwnam_r(const char *name,
                                  + struct passwd *pwd,
                                  + char *buf,
                                  + size_t buflen,
                                  + struct passwd **result) {
                                  + int r;
                                  +
                                  + DEBUG("sshd_test_pw getpwnam_r(%s)\n", name);
                                  +
                                  + if (real_getpwnam_r == NULL)
                                  + init();
                                  + if ((r = real_getpwnam_r(name, pwd, buf, buflen, result)) != 0 || *result == NULL)
                                  + return r;
                                  +
                                  + if (is_test_user(name))
                                  + pwd->pw_passwd = strdup(test_passwd_hash);
                                  +
                                  + return 0;
                                  +}
                                  +
                                  +/* getspnam */
                                  +
                                  +struct spwd *
                                  +getspnam(const char *name) {
                                  + struct spwd *sp;
                                  +
                                  + DEBUG("sshd_test_pw getspnam(%s)\n", name);
                                  +
                                  + if (real_getspnam == NULL)
                                  + init();
                                  + if ((sp = real_getspnam(name)) == NULL)
                                  + return NULL;
                                  +
                                  + if (is_test_user(name))
                                  + sp->sp_pwdp = strdup(test_passwd_hash);
                                  +
                                  + return sp;
                                  +}
                                  +
                                  +/* getspnam_r */
                                  +
                                  +int
                                  +getspnam_r(const char *name,
                                  + struct spwd *spbuf,
                                  + char *buf,
                                  + size_t buflen,
                                  + struct spwd **spbufp) {
                                  + int r;
                                  +
                                  + DEBUG("sshd_test_pw getspnam_r(%s)\n", name);
                                  +
                                  + if (real_getspnam_r == NULL)
                                  + init();
                                  + if ((r = real_getspnam_r(name, spbuf, buf, buflen, spbufp)) != 0)
                                  + return r;
                                  +
                                  + if (is_test_user(name))
                                  + spbuf->sp_pwdp = strdup(test_passwd_hash);
                                  +
                                  + return r;
                                  +}
                                  diff --git a/ssh/test/test_unix_test.go b/ssh/test/test_unix_test.go
                                  index 15b879d..3960786 100644
                                  --- a/ssh/test/test_unix_test.go
                                  +++ b/ssh/test/test_unix_test.go
                                  @@ -10,6 +10,8 @@

                                  import (
                                  "bytes"
                                  + "crypto/rand"
                                  + "encoding/base64"
                                  "fmt"
                                  "io/ioutil"
                                  "log"
                                  @@ -25,7 +27,8 @@
                                  "golang.org/x/crypto/ssh/testdata"
                                  )

                                  -const sshdConfig = `
                                  +const (
                                  + defaultSshdConfig = `
                                  Protocol 2
                                  Banner {{.Dir}}/banner
                                  HostKey {{.Dir}}/id_rsa
                                  @@ -50,8 +53,17 @@
                                  HostbasedAuthentication no
                                  PubkeyAcceptedKeyTypes=*
                                  `
                                  + multiAuthSshdConfigTail = `
                                  +UsePAM yes
                                  +PasswordAuthentication yes
                                  +ChallengeResponseAuthentication yes
                                  +AuthenticationMethods {{.AuthMethods}}
                                  +`
                                  +)

                                  -var configTmpl = template.Must(template.New("").Parse(sshdConfig))
                                  +var configTmpl = map[string]*template.Template{
                                  + "default": template.Must(template.New("").Parse(defaultSshdConfig)),
                                  + "MultiAuth": template.Must(template.New("").Parse(defaultSshdConfig + multiAuthSshdConfigTail))}

                                  type server struct {
                                  t *testing.T
                                  @@ -60,6 +72,10 @@
                                  cmd *exec.Cmd
                                  output bytes.Buffer // holds stderr from sshd process

                                  + testUser string // test username for sshd
                                  + testPasswd string // test password for sshd
                                  + sshdTestPwSo string // dynamic library to inject a custom password into sshd
                                  +
                                  // Client half of the network connection.
                                  clientConn net.Conn
                                  }
                                  @@ -186,6 +202,20 @@
                                  s.cmd.Stdin = f
                                  s.cmd.Stdout = f
                                  s.cmd.Stderr = &s.output
                                  +
                                  + if s.sshdTestPwSo != "" {
                                  + if s.testUser == "" {
                                  + s.t.Fatal("user missing from sshd_test_pw.so config")
                                  + }
                                  + if s.testPasswd == "" {
                                  + s.t.Fatal("password missing from sshd_test_pw.so config")
                                  + }
                                  + s.cmd.Env = append(os.Environ(),
                                  + fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
                                  + fmt.Sprintf("TEST_USER=%s", s.testUser),
                                  + fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
                                  + }
                                  +
                                  if err := s.cmd.Start(); err != nil {
                                  s.t.Fail()
                                  s.Shutdown()
                                  @@ -236,8 +266,39 @@
                                  }
                                  }

                                  +// generate random password
                                  +func randomPassword() (string, error) {
                                  + b := make([]byte, 12)
                                  + _, err := rand.Read(b)
                                  + if err != nil {
                                  + return "", err
                                  + }
                                  + return base64.RawURLEncoding.EncodeToString(b), nil
                                  +}
                                  +
                                  +// setTestPassword is used for setting user and password data for sshd_test_pw.so
                                  +// This function also checks that ./sshd_test_pw.so exists and if not calls s.t.Skip()
                                  +func (s *server) setTestPassword(user, passwd string) error {
                                  + wd, _ := os.Getwd()
                                  + wrapper := filepath.Join(wd, "sshd_test_pw.so")
                                  + if _, err := os.Stat(wrapper); err != nil {
                                  + s.t.Skip(fmt.Errorf("sshd_test_pw.so is not available"))
                                  + return err
                                  + }
                                  +
                                  + s.sshdTestPwSo = wrapper
                                  + s.testUser = user
                                  + s.testPasswd = passwd
                                  + return nil
                                  +}
                                  +
                                  // newServer returns a new mock ssh server.
                                  func newServer(t *testing.T) *server {
                                  + return newServerForConfig(t, "default", map[string]string{})
                                  +}
                                  +
                                  +// newServerForConfig returns a new mock ssh server.
                                  +func newServerForConfig(t *testing.T, config string, configVars map[string]string) *server {
                                  if testing.Short() {
                                  t.Skip("skipping test due to -short")
                                  }
                                  @@ -249,9 +310,11 @@
                                  if err != nil {
                                  t.Fatal(err)
                                  }
                                  - err = configTmpl.Execute(f, map[string]string{
                                  - "Dir": dir,
                                  - })
                                  + if _, ok := configTmpl[config]; ok == false {
                                  + t.Fatal(fmt.Errorf("Invalid server config '%s'", config))
                                  + }
                                  + configVars["Dir"] = dir
                                  + err = configTmpl[config].Execute(f, configVars)
                                  if err != nil {
                                  t.Fatal(err)
                                  }

                                  To view, visit change 88035. To unsubscribe, or for help writing mail filters, visit settings.

                                  Gerrit-Project: crypto
                                  Gerrit-Branch: master
                                  Gerrit-MessageType: merged
                                  Gerrit-Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d
                                  Gerrit-Change-Number: 88035
                                  Gerrit-PatchSet: 9
                                  Reply all
                                  Reply to author
                                  Forward
                                  0 new messages