Nils PonsArDD
Nils PonsArDD

Reputation: 31

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

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 (
    "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)
            }
        }()
    }
}

Upvotes: 1

Views: 1263

Answers (2)

Nils PonsArDD
Nils PonsArDD

Reputation: 31

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 => 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.

Upvotes: 2

Wolfgang
Wolfgang

Reputation: 1388

This is the easy way to do it, let letsencrypt handle the certificates for you :)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/index", index)
    certManager := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("www.example.com"), // replace with your domain
        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("", ""))
}

Upvotes: 0

Related Questions