Skip to content

Commit

Permalink
ssh: use extension negotiation (rfc 8308) in ssh clients
Browse files Browse the repository at this point in the history
Wrap ssh-rsa keys directly loaded or exposed via the ssh agent with the
corresponding signer algorithm and avoid using algorithms which are not
supported on the server side to reduce the amount of failed
authentication requests.

Signed-off-by: Roman Mohr <[email protected]>
  • Loading branch information
rmohr committed Dec 3, 2021
1 parent 9e543a4 commit e4ed966
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 0 deletions.
16 changes: 16 additions & 0 deletions ssh/agent/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"sync"

"crypto"

"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
Expand Down Expand Up @@ -784,6 +785,21 @@ func (s *agentKeyringSigner) SignWithOpts(rand io.Reader, data []byte, opts cryp
return s.agent.SignWithFlags(s.pub, data, flags)
}

func (s *agentKeyringSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
// The agent has its own entropy source, so the rand argument is ignored.
if s.PublicKey().Type() != ssh.SigAlgoRSA {
return nil, fmt.Errorf("public key must be of type ssh-rsa, but got %v", s.PublicKey().Type())
}

switch algorithm {
case ssh.SigAlgoRSASHA2256:
return s.agent.SignWithFlags(s.PublicKey(), data, SignatureFlagRsaSha256)
case ssh.SigAlgoRSASHA2512:
return s.agent.SignWithFlags(s.PublicKey(), data, SignatureFlagRsaSha512)
}
return nil, fmt.Errorf("algorithm not supported")
}

// Calls an extension method. It is up to the agent implementation as to whether or not
// any particular extension is supported and may always return an error. Because the
// type of the response is up to the implementation, this returns the bytes of the
Expand Down
29 changes: 29 additions & 0 deletions ssh/client_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"strings"
)

type authResult int
Expand Down Expand Up @@ -217,8 +218,36 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
if err != nil {
return authFailure, nil, err
}
supportedKeyTypes := map[string]struct{}{}
if t, ok := c.(*handshakeTransport); ok {
algos := t.extensions[ExtServerSigAlgs]
if len(algos) > 0 {
for _, algo := range strings.Split(string(algos), ",") {
supportedKeyTypes[algo] = struct{}{}
}
}
}

var methods []string
for _, signer := range signers {
// skip trying unsupported algorithms to reduce the amount of failed auth requests
// since failed auth requests can lead to being blocked before a working config is found.
if _, exists := supportedKeyTypes[signer.PublicKey().Type()]; !exists {
continue
}

// Even servers which do not accept `ssh-rsa` "offer" it potentially in the extension.
// Choose the best available alternative if alternatives are available to avoid being rejected.
if s, ok := signer.(AlgorithmSigner); ok {
if signer.PublicKey().Type() == SigAlgoRSA {
if _, exists := supportedKeyTypes[SigAlgoRSASHA2512]; exists {
signer = &rsaSigner{s, SigAlgoRSASHA2512}
} else if _, exists := supportedKeyTypes[SigAlgoRSASHA2256]; exists {
signer = &rsaSigner{s, SigAlgoRSASHA2256}
}
}
}

ok, err := validateKey(signer.PublicKey(), user, c)
if err != nil {
return authFailure, nil, err
Expand Down
21 changes: 21 additions & 0 deletions ssh/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,27 @@ func (s *rsaSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, s.defaultAlgorithm)
}

func (s *rsaSigner) PublicKey() PublicKey {
return &wrappedPublicKey{wrapped: s.AlgorithmSigner.PublicKey(), algo: s.defaultAlgorithm}
}

type wrappedPublicKey struct {
wrapped PublicKey
algo string
}

func (w *wrappedPublicKey) Type() string {
return w.algo
}

func (w *wrappedPublicKey) Marshal() []byte {
return w.wrapped.Marshal()
}

func (w *wrappedPublicKey) Verify(data []byte, sig *Signature) error {
return w.wrapped.Verify(data, sig)
}

type wrappedSigner struct {
signer crypto.Signer
pubKey PublicKey
Expand Down

0 comments on commit e4ed966

Please sign in to comment.