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