// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ssh_test import ( "bufio" "bytes" "crypto/rand" "crypto/rsa" "fmt" "log" "net" "net/http" "os" "path/filepath" "strings" "sync" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) func ExampleNewServerConn() { // Public key authentication is done by comparing // the public key of a received connection // with the entries in the authorized_keys file. authorizedKeysBytes, err := os.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 } // An SSH server is represented by a ServerConfig, which holds // certificate details and handles authentication of ServerConns. config := &ssh.ServerConfig{ // Remove to disable password auth. PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { // Should use constant-time compare (or better, salt+hash) in // a production setting. if c.User() == "testuser" && string(pass) == "tiger" { return nil, nil } return nil, fmt.Errorf("password rejected for %q", c.User()) }, // Remove to disable public key auth. 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 := os.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) // Once a ServerConfig has been configured, connections can be // accepted. 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) } // Before use, a handshake must be performed on the incoming // net.Conn. 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"]) var wg sync.WaitGroup defer wg.Wait() // The incoming Request channel must be serviced. wg.Add(1) go func() { ssh.DiscardRequests(reqs) wg.Done() }() // Service the incoming Channel channel. for newChannel := range chans { // Channels have a type, depending on the application level // protocol intended. In the case of a shell, the type is // "session" and ServerShell may be used to present a simple // terminal interface. 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) } // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "shell" request. wg.Add(1) go func(in <-chan *ssh.Request) { for req := range in { req.Reply(req.Type == "shell", nil) } wg.Done() }(requests) term := terminal.NewTerminal(channel, "> ") wg.Add(1) go func() { defer func() { channel.Close() wg.Done() }() for { line, err := term.ReadLine() if err != nil { break } fmt.Println(line) } }() } } func ExampleServerConfig_AddHostKey() { // Minimal ServerConfig supporting only password authentication. config := &ssh.ServerConfig{ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { // Should use constant-time compare (or better, salt+hash) in // a production setting. if c.User() == "testuser" && string(pass) == "tiger" { return nil, nil } return nil, fmt.Errorf("password rejected for %q", c.User()) }, } privateBytes, err := os.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) } // Restrict host key algorithms to disable ssh-rsa. signer, err := ssh.NewSignerWithAlgorithms(private.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512}) if err != nil { log.Fatal("Failed to create private key with restricted algorithms: ", err) } config.AddHostKey(signer) } func ExampleClientConfig_HostKeyCallback() { // Every client must provide a host key check. Here is a // simple-minded parse of OpenSSH's known_hosts file host := "hostname" file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")) if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) var hostKey ssh.PublicKey for scanner.Scan() { fields := strings.Split(scanner.Text(), " ") if len(fields) != 3 { continue } if strings.Contains(fields[0], host) { var err error hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes()) if err != nil { log.Fatalf("error parsing %q: %v", fields[2], err) } break } } if hostKey == nil { log.Fatalf("no hostkey for %s", host) } config := ssh.ClientConfig{ User: os.Getenv("USER"), HostKeyCallback: ssh.FixedHostKey(hostKey), } _, err = ssh.Dial("tcp", host+":22", &config) log.Println(err) } func ExampleDial() { var hostKey ssh.PublicKey // An SSH client is represented with a ClientConn. // // To authenticate with the remote server you must pass at least one // implementation of AuthMethod via the Auth field in ClientConfig, // and provide a HostKeyCallback. config := &ssh.ClientConfig{ User: "username", Auth: []ssh.AuthMethod{ ssh.Password("yourpassword"), }, HostKeyCallback: ssh.FixedHostKey(hostKey), } client, err := ssh.Dial("tcp", "yourserver.com:22", config) if err != nil { log.Fatal("Failed to dial: ", err) } defer client.Close() // Each ClientConn can support multiple interactive sessions, // represented by a Session. session, err := client.NewSession() if err != nil { log.Fatal("Failed to create session: ", err) } defer session.Close() // Once a Session is created, you can execute a single command on // the remote side using the Run method. var b bytes.Buffer session.Stdout = &b if err := session.Run("/usr/bin/whoami"); err != nil { log.Fatal("Failed to run: " + err.Error()) } fmt.Println(b.String()) } func ExamplePublicKeys() { var hostKey ssh.PublicKey // A public key may be used to authenticate against the remote // server by using an unencrypted PEM-encoded private key file. // // If you have an encrypted private key, the crypto/x509 package // can be used to decrypt it. key, err := os.ReadFile("/home/user/.ssh/id_rsa") if err != nil { log.Fatalf("unable to read private key: %v", err) } // Create the Signer for this private key. signer, err := ssh.ParsePrivateKey(key) if err != nil { log.Fatalf("unable to parse private key: %v", err) } config := &ssh.ClientConfig{ User: "user", Auth: []ssh.AuthMethod{ // Use the PublicKeys method for remote authentication. ssh.PublicKeys(signer), }, HostKeyCallback: ssh.FixedHostKey(hostKey), } // Connect to the remote server and perform the SSH handshake. client, err := ssh.Dial("tcp", "host.com:22", config) if err != nil { log.Fatalf("unable to connect: %v", err) } defer client.Close() } func ExampleClient_Listen() { var hostKey ssh.PublicKey config := &ssh.ClientConfig{ User: "username", Auth: []ssh.AuthMethod{ ssh.Password("password"), }, HostKeyCallback: ssh.FixedHostKey(hostKey), } // Dial your ssh server. conn, err := ssh.Dial("tcp", "localhost:22", config) if err != nil { log.Fatal("unable to connect: ", err) } defer conn.Close() // Request the remote side to open port 8080 on all interfaces. l, err := conn.Listen("tcp", "0.0.0.0:8080") if err != nil { log.Fatal("unable to register tcp forward: ", err) } defer l.Close() // Serve HTTP with your SSH server acting as a reverse proxy. http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintf(resp, "Hello world!\n") })) } func ExampleSession_RequestPty() { var hostKey ssh.PublicKey // Create client config config := &ssh.ClientConfig{ User: "username", Auth: []ssh.AuthMethod{ ssh.Password("password"), }, HostKeyCallback: ssh.FixedHostKey(hostKey), } // Connect to ssh server conn, err := ssh.Dial("tcp", "localhost:22", config) if err != nil { log.Fatal("unable to connect: ", err) } defer conn.Close() // Create a session session, err := conn.NewSession() if err != nil { log.Fatal("unable to create session: ", err) } defer session.Close() // Set up terminal modes modes := ssh.TerminalModes{ ssh.ECHO: 0, // disable echoing ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud } // Request pseudo terminal if err := session.RequestPty("xterm", 40, 80, modes); err != nil { log.Fatal("request for pseudo terminal failed: ", err) } // Start remote shell if err := session.Shell(); err != nil { log.Fatal("failed to start shell: ", err) } } func ExampleCertificate_SignCert() { // Sign a certificate with a specific algorithm. privateKey, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { log.Fatal("unable to generate RSA key: ", err) } publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) if err != nil { log.Fatal("unable to get RSA public key: ", err) } caKey, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { log.Fatal("unable to generate CA key: ", err) } signer, err := ssh.NewSignerFromKey(caKey) if err != nil { log.Fatal("unable to generate signer from key: ", err) } mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256}) if err != nil { log.Fatal("unable to create signer with algoritms: ", err) } certificate := ssh.Certificate{ Key: publicKey, CertType: ssh.UserCert, } if err := certificate.SignCert(rand.Reader, mas); err != nil { log.Fatal("unable to sign certificate: ", err) } // Save the public key to a file and check that rsa-sha-256 is used for // signing: // ssh-keygen -L -f fmt.Println(string(ssh.MarshalAuthorizedKey(&certificate))) }