diff options
Diffstat (limited to 'ssh/test')
| -rw-r--r-- | ssh/test/server_test.go | 98 | ||||
| -rw-r--r-- | ssh/test/sshcli_test.go | 96 |
2 files changed, 194 insertions, 0 deletions
diff --git a/ssh/test/server_test.go b/ssh/test/server_test.go new file mode 100644 index 0000000..e70241b --- /dev/null +++ b/ssh/test/server_test.go @@ -0,0 +1,98 @@ +// Copyright 2023 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 test + +import ( + "net" + + "golang.org/x/crypto/ssh" +) + +type exitStatusMsg struct { + Status uint32 +} + +// goServer is a test Go SSH server that accepts public key and certificate +// authentication and replies with a 0 exit status to any exec request without +// running any commands. +type goTestServer struct { + listener net.Listener + config *ssh.ServerConfig + done <-chan struct{} +} + +func newTestServer(config *ssh.ServerConfig) (*goTestServer, error) { + server := &goTestServer{ + config: config, + } + listener, err := net.Listen("tcp", "127.0.0.1:") + if err != nil { + return nil, err + } + server.listener = listener + done := make(chan struct{}, 1) + server.done = done + go server.acceptConnections(done) + + return server, nil +} + +func (s *goTestServer) port() (string, error) { + _, port, err := net.SplitHostPort(s.listener.Addr().String()) + return port, err +} + +func (s *goTestServer) acceptConnections(done chan<- struct{}) { + defer close(done) + + for { + c, err := s.listener.Accept() + if err != nil { + return + } + _, chans, reqs, err := ssh.NewServerConn(c, s.config) + if err != nil { + return + } + go ssh.DiscardRequests(reqs) + defer c.Close() + + for newChannel := range chans { + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + continue + } + + channel, requests, err := newChannel.Accept() + if err != nil { + continue + } + + go func(in <-chan *ssh.Request) { + for req := range in { + ok := false + switch req.Type { + case "exec": + ok = true + go func() { + channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatusMsg{Status: 0})) + channel.Close() + }() + } + if req.WantReply { + req.Reply(ok, nil) + } + } + }(requests) + } + } +} + +func (s *goTestServer) Close() error { + err := s.listener.Close() + // wait for the accept loop to exit + <-s.done + return err +} diff --git a/ssh/test/sshcli_test.go b/ssh/test/sshcli_test.go new file mode 100644 index 0000000..d3b85d7 --- /dev/null +++ b/ssh/test/sshcli_test.go @@ -0,0 +1,96 @@ +// Copyright 2023 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 test + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "golang.org/x/crypto/internal/testenv" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +func sshClient(t *testing.T) string { + if testing.Short() { + t.Skip("Skipping test that executes OpenSSH in -short mode") + } + sshCLI := os.Getenv("SSH_CLI_PATH") + if sshCLI == "" { + sshCLI = "ssh" + } + var err error + sshCLI, err = exec.LookPath(sshCLI) + if err != nil { + t.Skipf("Can't find an ssh(1) client to test against: %v", err) + } + return sshCLI +} + +func TestSSHCLIAuth(t *testing.T) { + sshCLI := sshClient(t) + dir := t.TempDir() + keyPrivPath := filepath.Join(dir, "rsa") + + for fn, content := range map[string][]byte{ + keyPrivPath: testdata.PEMBytes["rsa"], + keyPrivPath + ".pub": ssh.MarshalAuthorizedKey(testPublicKeys["rsa"]), + filepath.Join(dir, "rsa-cert.pub"): testdata.SSHCertificates["rsa-user-testcertificate"], + } { + if err := os.WriteFile(fn, content, 0600); err != nil { + t.Fatalf("WriteFile(%q): %v", fn, err) + } + } + + certChecker := ssh.CertChecker{ + IsUserAuthority: func(k ssh.PublicKey) bool { + return bytes.Equal(k.Marshal(), testPublicKeys["ca"].Marshal()) + }, + UserKeyFallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + if conn.User() == "testpubkey" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { + return nil, nil + } + + return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User()) + }, + } + + config := &ssh.ServerConfig{ + PublicKeyCallback: certChecker.Authenticate, + } + config.AddHostKey(testSigners["rsa"]) + + server, err := newTestServer(config) + if err != nil { + t.Fatalf("unable to start test server: %v", err) + } + defer server.Close() + + port, err := server.port() + if err != nil { + t.Fatalf("unable to get server port: %v", err) + } + + // test public key authentication. + cmd := testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no", + "-p", port, "testpubkey@127.0.0.1", "true") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("public key authentication failed, error: %v, command output %q", err, string(out)) + } + // Test SSH user certificate authentication. + // The username must match one of the principals included in the certificate. + // The certificate "rsa-user-testcertificate" has "testcertificate" as principal. + cmd = testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no", + "-p", port, "testcertificate@127.0.0.1", "true") + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("user certificate authentication failed, error: %v, command output %q", err, string(out)) + } +} |
