如何在Go中提供除RSA之外的公钥类型的SSH服务器?

huangapple go评论85阅读模式
英文:

ssh server in go : how to offer public key types different than rsa?

问题

我正在尝试使用x/crypto/ssh模块在Go语言中创建一个SSH服务器,但是我无法使公钥身份验证工作。我尝试了ssh/example_test.go文件中的ExampleNewServerConn()函数(在https://go.googlesource.com/crypto仓库中),但是公钥方法不起作用,看起来服务器没有广告正确的算法,因为当我尝试使用SSH客户端连接时,会出现以下行:

debug1: send_pubkey_test: no mutual signature algorithm

如果我添加-o PubkeyAcceptedKeyTypes=+ssh-rsa,公钥登录就可以工作,但是这种RSA方法已经被弃用了,我想使用其他公钥类型,我该如何做到?

谢谢提前帮助。

编辑:这是我用来测试的代码

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net"

	"golang.org/x/crypto/ssh"
	terminal "golang.org/x/term"
)

func main() {
	authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
	if err != nil {
		log.Fatalf("Failed to load authorized_keys, err: %v", err)
	}

	authorizedKeysMap := map[string]bool{}
	for len(authorizedKeysBytes) > 0 {
		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
		if err != nil {
			log.Fatal(err)
		}

		authorizedKeysMap[string(pubKey.Marshal())] = true
		authorizedKeysBytes = rest
	}
	config := &ssh.ServerConfig{
		PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
			if c.User() == "testuser" && string(pass) == "tiger" {
				return nil, nil
			}
			return nil, fmt.Errorf("password rejected for %q", c.User())
		},
		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
			if authorizedKeysMap[string(pubKey.Marshal())] {
				return &ssh.Permissions{
					// Record the public key used for authentication.
					Extensions: map[string]string{
						"pubkey-fp": ssh.FingerprintSHA256(pubKey),
					},
				}, nil
			}
			return nil, fmt.Errorf("unknown public key for %q", c.User())
		},
	}

	privateBytes, err := ioutil.ReadFile("id_rsa")
	if err != nil {
		log.Fatal("Failed to load private key: ", err)
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		log.Fatal("Failed to parse private key: ", err)
	}

	config.AddHostKey(private)

	listener, err := net.Listen("tcp", "0.0.0.0:2022")
	if err != nil {
		log.Fatal("failed to listen for connection: ", err)
	}
	nConn, err := listener.Accept()
	if err != nil {
		log.Fatal("failed to accept incoming connection: ", err)
	}

	conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
	if err != nil {
		log.Fatal("failed to handshake: ", err)
	}
	log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])

	go ssh.DiscardRequests(reqs)

	for newChannel := range chans {

		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			log.Fatalf("Could not accept channel: %v", err)
		}

		go func(in <-chan *ssh.Request) {
			for req := range in {
				req.Reply(req.Type == "shell", nil)
			}
		}(requests)

		term := terminal.NewTerminal(channel, "> ")

		go func() {
			defer channel.Close()
			for {
				line, err := term.ReadLine()
				if err != nil {
					break
				}
				fmt.Println(line)
			}
		}()
	}
}
英文:

I’m trying to create a ssh server in go using the x/crypto/ssh module but i can’t manage to make the public key authentification work.
I tried the ExampleNewServerConn() function in the ssh/example_test.go file (in the https://go.googlesource.com/crypto repo) but the public key method doesn’t work, it looks like the server isn’t advertising the right algorithms because i get this line when trying to connect with a ssh client :

debug1: send_pubkey_test: no mutual signature algorithm

If i add -o PubkeyAcceptedKeyTypes=+ssh-rsa the public key login works, but this rsa method is deprecated, i would like to use another public key type, how can i do that ?

Thanks in advance.

Edit : here is the code that i used to test

package main

import (
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;log&quot;
	&quot;net&quot;

	&quot;golang.org/x/crypto/ssh&quot;
	terminal &quot;golang.org/x/term&quot;
)

func main() {
	authorizedKeysBytes, err := ioutil.ReadFile(&quot;authorized_keys&quot;)
	if err != nil {
		log.Fatalf(&quot;Failed to load authorized_keys, err: %v&quot;, err)
	}

	authorizedKeysMap := map[string]bool{}
	for len(authorizedKeysBytes) &gt; 0 {
		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
		if err != nil {
			log.Fatal(err)
		}

		authorizedKeysMap[string(pubKey.Marshal())] = true
		authorizedKeysBytes = rest
	}
	config := &amp;ssh.ServerConfig{
		PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
			if c.User() == &quot;testuser&quot; &amp;&amp; string(pass) == &quot;tiger&quot; {
				return nil, nil
			}
			return nil, fmt.Errorf(&quot;password rejected for %q&quot;, c.User())
		},
		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
			if authorizedKeysMap[string(pubKey.Marshal())] {
				return &amp;ssh.Permissions{
					// Record the public key used for authentication.
					Extensions: map[string]string{
						&quot;pubkey-fp&quot;: ssh.FingerprintSHA256(pubKey),
					},
				}, nil
			}
			return nil, fmt.Errorf(&quot;unknown public key for %q&quot;, c.User())
		},
	}

	privateBytes, err := ioutil.ReadFile(&quot;id_rsa&quot;)
	if err != nil {
		log.Fatal(&quot;Failed to load private key: &quot;, err)
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		log.Fatal(&quot;Failed to parse private key: &quot;, err)
	}

	config.AddHostKey(private)

	listener, err := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:2022&quot;)
	if err != nil {
		log.Fatal(&quot;failed to listen for connection: &quot;, err)
	}
	nConn, err := listener.Accept()
	if err != nil {
		log.Fatal(&quot;failed to accept incoming connection: &quot;, err)
	}

	conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
	if err != nil {
		log.Fatal(&quot;failed to handshake: &quot;, err)
	}
	log.Printf(&quot;logged in with key %s&quot;, conn.Permissions.Extensions[&quot;pubkey-fp&quot;])

	go ssh.DiscardRequests(reqs)

	for newChannel := range chans {

		if newChannel.ChannelType() != &quot;session&quot; {
			newChannel.Reject(ssh.UnknownChannelType, &quot;unknown channel type&quot;)
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			log.Fatalf(&quot;Could not accept channel: %v&quot;, err)
		}

		go func(in &lt;-chan *ssh.Request) {
			for req := range in {
				req.Reply(req.Type == &quot;shell&quot;, nil)
			}
		}(requests)

		term := terminal.NewTerminal(channel, &quot;&gt; &quot;)

		go func() {
			defer channel.Close()
			for {
				line, err := term.ReadLine()
				if err != nil {
					break
				}
				fmt.Println(line)
			}
		}()
	}
}

答案1

得分: 2

我发现客户端和服务器无法通信的原因是x/crypto库中尚未实现rsa-sha2算法。在GitHub上有一个相关的问题:https://github.com/golang/go/issues/49952。

一个临时解决方案是在你的go.mod文件末尾添加以下内容:

replace golang.org/x/crypto => github.com/rmohr/crypto v0.0.0-20211203105847-e4ed9664ac54

这将使用@rmohr的x/crypto分支,它支持rsa-sha2算法。

英文:

I found why the client and the server can’t communicate, the rsa-sha2 algorithms are not yet implemented in the x/crypto library. There is an issue about it on github : https://github.com/golang/go/issues/49952 .

A temporary solution is to add

replace golang.org/x/crypto =&gt; github.com/rmohr/crypto v0.0.0-20211203105847-e4ed9664ac54

at the end of your go.mod file, it uses a x/crypto fork from @rmohr that works with rsa-sha2.

答案2

得分: 0

这是一个简单的方法,让letsencrypt为您处理证书 如何在Go中提供除RSA之外的公钥类型的SSH服务器?

func main() {
r := mux.NewRouter()
r.HandleFunc("/index", index)
certManager := autocert.Manager{
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("www.example.com"), // 替换为您的域名
Cache:      autocert.DirCache("certs"),
}
srv := &http.Server{
Handler:      r,
Addr:         ":https",
WriteTimeout: 5 * time.Second,
ReadTimeout:  5 * time.Second,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) //nolint
log.Fatal(srv.ListenAndServeTLS("", ""))
}
英文:

This is the easy way to do it, let letsencrypt handle the certificates for you 如何在Go中提供除RSA之外的公钥类型的SSH服务器?

func main() {
r := mux.NewRouter()
r.HandleFunc(&quot;/index&quot;, index)
certManager := autocert.Manager{
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(&quot;www.example.com&quot;), // replace with your domain
Cache:      autocert.DirCache(&quot;certs&quot;),
}
srv := &amp;http.Server{
Handler:      r,
Addr:         &quot;:https&quot;,
WriteTimeout: 5 * time.Second,
ReadTimeout:  5 * time.Second,
TLSConfig: &amp;tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(&quot;:http&quot;, certManager.HTTPHandler(nil)) //nolint
log.Fatal(srv.ListenAndServeTLS(&quot;&quot;, &quot;&quot;))
}

huangapple
  • 本文由 发表于 2021年12月9日 22:51:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/70291932.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定