From e4ed9664ac545e290a287dc913e01b6939c837ea Mon Sep 17 00:00:00 2001 From: Roman Mohr Date: Fri, 3 Dec 2021 11:58:35 +0100 Subject: [PATCH] ssh: use extension negotiation (rfc 8308) in ssh clients 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 --- ssh/agent/client.go | 16 ++++++++++++++++ ssh/client_auth.go | 29 +++++++++++++++++++++++++++++ ssh/keys.go | 21 +++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/ssh/agent/client.go b/ssh/agent/client.go index b909471cc0..d722928c6f 100644 --- a/ssh/agent/client.go +++ b/ssh/agent/client.go @@ -26,6 +26,7 @@ import ( "sync" "crypto" + "golang.org/x/crypto/ed25519" "golang.org/x/crypto/ssh" ) @@ -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 diff --git a/ssh/client_auth.go b/ssh/client_auth.go index e864a139a1..f101d16388 100644 --- a/ssh/client_auth.go +++ b/ssh/client_auth.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "strings" ) type authResult int @@ -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 diff --git a/ssh/keys.go b/ssh/keys.go index c67d3a31cb..3a17f9a579 100644 --- a/ssh/keys.go +++ b/ssh/keys.go @@ -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