Skip to content

Commit

Permalink
ssh: improve client public key authentication
Browse files Browse the repository at this point in the history
Previously, the public key authentication for clients would send an
enquiry to the remote for every key specified before attempting to
authenticate with the server.

Now, we immediately try to authenticate once a valid key is found.
This results in exchanging fewer packets if the valid key is near the
top of the list. If all keys fail, then the number of packets exchanged
by the client and server is unaffected.

For OpenSSH daemon, an enquiry into the validity of a key without
authentication is still recorded as an authentication attempt, so any
clients with more than MaxAuthTries public keys would not be able to
authenticate using the previous implementation. This change will allow
clients to succeed authentication if the successful key is at the start
of the list of keys.

Change-Id: I8ea42caf40c0864752218c3f6934e86b12f5b81a
Reviewed-on: https://go-review.googlesource.com/38890
Reviewed-by: Adam Langley <[email protected]>
  • Loading branch information
desdeel2d0m authored and agl committed Apr 3, 2017
1 parent c798294 commit 0560d96
Showing 1 changed file with 28 additions and 17 deletions.
45 changes: 28 additions & 17 deletions ssh/client_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,31 +179,26 @@ func (cb publicKeyCallback) method() string {
}

func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an
// enquiry to test if each key is acceptable to the remote. The second
// stage attempts to authenticate with the valid keys obtained in the
// first stage.
// 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
// the process with the remaining keys.

signers, err := cb()
if err != nil {
return false, nil, err
}
var validKeys []Signer
var methods []string
for _, signer := range signers {
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
validKeys = append(validKeys, signer)
} else {
if err != nil {
return false, nil, err
}
ok, err := validateKey(signer.PublicKey(), user, c)
if err != nil {
return false, nil, err
}
if !ok {
continue
}
}

// methods that may continue if this auth is not successful.
var methods []string
for _, signer := range validKeys {
pub := signer.PublicKey()

pubKey := pub.Marshal()
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
User: user,
Expand Down Expand Up @@ -236,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
if err != nil {
return false, nil, err
}
if success {

// 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()) {
return success, methods, err
}
}

return false, methods, nil
}

func containsMethod(methods []string, method string) bool {
for _, m := range methods {
if m == method {
return true
}
}

return false
}

// validateKey validates the key provided is acceptable to the server.
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
pubKey := key.Marshal()
Expand Down

0 comments on commit 0560d96

Please sign in to comment.