mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
774 lines
19 KiB
774 lines
19 KiB
// 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 |
|
|
|
// Session tests. |
|
|
|
import ( |
|
"bytes" |
|
crypto_rand "crypto/rand" |
|
"errors" |
|
"io" |
|
"io/ioutil" |
|
"math/rand" |
|
"net" |
|
"testing" |
|
|
|
"github.com/gogits/gogs/modules/crypto/ssh/terminal" |
|
) |
|
|
|
type serverType func(Channel, <-chan *Request, *testing.T) |
|
|
|
// dial constructs a new test server and returns a *ClientConn. |
|
func dial(handler serverType, t *testing.T) *Client { |
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
|
|
go func() { |
|
defer c1.Close() |
|
conf := ServerConfig{ |
|
NoClientAuth: true, |
|
} |
|
conf.AddHostKey(testSigners["rsa"]) |
|
|
|
_, chans, reqs, err := NewServerConn(c1, &conf) |
|
if err != nil { |
|
t.Fatalf("Unable to handshake: %v", err) |
|
} |
|
go DiscardRequests(reqs) |
|
|
|
for newCh := range chans { |
|
if newCh.ChannelType() != "session" { |
|
newCh.Reject(UnknownChannelType, "unknown channel type") |
|
continue |
|
} |
|
|
|
ch, inReqs, err := newCh.Accept() |
|
if err != nil { |
|
t.Errorf("Accept: %v", err) |
|
continue |
|
} |
|
go func() { |
|
handler(ch, inReqs, t) |
|
}() |
|
} |
|
}() |
|
|
|
config := &ClientConfig{ |
|
User: "testuser", |
|
} |
|
|
|
conn, chans, reqs, err := NewClientConn(c2, "", config) |
|
if err != nil { |
|
t.Fatalf("unable to dial remote side: %v", err) |
|
} |
|
|
|
return NewClient(conn, chans, reqs) |
|
} |
|
|
|
// Test a simple string is returned to session.Stdout. |
|
func TestSessionShell(t *testing.T) { |
|
conn := dial(shellHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
stdout := new(bytes.Buffer) |
|
session.Stdout = stdout |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %s", err) |
|
} |
|
if err := session.Wait(); err != nil { |
|
t.Fatalf("Remote command did not exit cleanly: %v", err) |
|
} |
|
actual := stdout.String() |
|
if actual != "golang" { |
|
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) |
|
} |
|
} |
|
|
|
// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it. |
|
|
|
// Test a simple string is returned via StdoutPipe. |
|
func TestSessionStdoutPipe(t *testing.T) { |
|
conn := dial(shellHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
stdout, err := session.StdoutPipe() |
|
if err != nil { |
|
t.Fatalf("Unable to request StdoutPipe(): %v", err) |
|
} |
|
var buf bytes.Buffer |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
done := make(chan bool, 1) |
|
go func() { |
|
if _, err := io.Copy(&buf, stdout); err != nil { |
|
t.Errorf("Copy of stdout failed: %v", err) |
|
} |
|
done <- true |
|
}() |
|
if err := session.Wait(); err != nil { |
|
t.Fatalf("Remote command did not exit cleanly: %v", err) |
|
} |
|
<-done |
|
actual := buf.String() |
|
if actual != "golang" { |
|
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) |
|
} |
|
} |
|
|
|
// Test that a simple string is returned via the Output helper, |
|
// and that stderr is discarded. |
|
func TestSessionOutput(t *testing.T) { |
|
conn := dial(fixedOutputHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
|
|
buf, err := session.Output("") // cmd is ignored by fixedOutputHandler |
|
if err != nil { |
|
t.Error("Remote command did not exit cleanly:", err) |
|
} |
|
w := "this-is-stdout." |
|
g := string(buf) |
|
if g != w { |
|
t.Error("Remote command did not return expected string:") |
|
t.Logf("want %q", w) |
|
t.Logf("got %q", g) |
|
} |
|
} |
|
|
|
// Test that both stdout and stderr are returned |
|
// via the CombinedOutput helper. |
|
func TestSessionCombinedOutput(t *testing.T) { |
|
conn := dial(fixedOutputHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
|
|
buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler |
|
if err != nil { |
|
t.Error("Remote command did not exit cleanly:", err) |
|
} |
|
const stdout = "this-is-stdout." |
|
const stderr = "this-is-stderr." |
|
g := string(buf) |
|
if g != stdout+stderr && g != stderr+stdout { |
|
t.Error("Remote command did not return expected string:") |
|
t.Logf("want %q, or %q", stdout+stderr, stderr+stdout) |
|
t.Logf("got %q", g) |
|
} |
|
} |
|
|
|
// Test non-0 exit status is returned correctly. |
|
func TestExitStatusNonZero(t *testing.T) { |
|
conn := dial(exitStatusNonZeroHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err == nil { |
|
t.Fatalf("expected command to fail but it didn't") |
|
} |
|
e, ok := err.(*ExitError) |
|
if !ok { |
|
t.Fatalf("expected *ExitError but got %T", err) |
|
} |
|
if e.ExitStatus() != 15 { |
|
t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus()) |
|
} |
|
} |
|
|
|
// Test 0 exit status is returned correctly. |
|
func TestExitStatusZero(t *testing.T) { |
|
conn := dial(exitStatusZeroHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
|
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err != nil { |
|
t.Fatalf("expected nil but got %v", err) |
|
} |
|
} |
|
|
|
// Test exit signal and status are both returned correctly. |
|
func TestExitSignalAndStatus(t *testing.T) { |
|
conn := dial(exitSignalAndStatusHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err == nil { |
|
t.Fatalf("expected command to fail but it didn't") |
|
} |
|
e, ok := err.(*ExitError) |
|
if !ok { |
|
t.Fatalf("expected *ExitError but got %T", err) |
|
} |
|
if e.Signal() != "TERM" || e.ExitStatus() != 15 { |
|
t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus()) |
|
} |
|
} |
|
|
|
// Test exit signal and status are both returned correctly. |
|
func TestKnownExitSignalOnly(t *testing.T) { |
|
conn := dial(exitSignalHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err == nil { |
|
t.Fatalf("expected command to fail but it didn't") |
|
} |
|
e, ok := err.(*ExitError) |
|
if !ok { |
|
t.Fatalf("expected *ExitError but got %T", err) |
|
} |
|
if e.Signal() != "TERM" || e.ExitStatus() != 143 { |
|
t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus()) |
|
} |
|
} |
|
|
|
// Test exit signal and status are both returned correctly. |
|
func TestUnknownExitSignal(t *testing.T) { |
|
conn := dial(exitSignalUnknownHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err == nil { |
|
t.Fatalf("expected command to fail but it didn't") |
|
} |
|
e, ok := err.(*ExitError) |
|
if !ok { |
|
t.Fatalf("expected *ExitError but got %T", err) |
|
} |
|
if e.Signal() != "SYS" || e.ExitStatus() != 128 { |
|
t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus()) |
|
} |
|
} |
|
|
|
// Test WaitMsg is not returned if the channel closes abruptly. |
|
func TestExitWithoutStatusOrSignal(t *testing.T) { |
|
conn := dial(exitWithoutSignalOrStatus, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatalf("Unable to request new session: %v", err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err == nil { |
|
t.Fatalf("expected command to fail but it didn't") |
|
} |
|
_, ok := err.(*ExitError) |
|
if ok { |
|
// you can't actually test for errors.errorString |
|
// because it's not exported. |
|
t.Fatalf("expected *errorString but got %T", err) |
|
} |
|
} |
|
|
|
// windowTestBytes is the number of bytes that we'll send to the SSH server. |
|
const windowTestBytes = 16000 * 200 |
|
|
|
// TestServerWindow writes random data to the server. The server is expected to echo |
|
// the same data back, which is compared against the original. |
|
func TestServerWindow(t *testing.T) { |
|
origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes)) |
|
io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes) |
|
origBytes := origBuf.Bytes() |
|
|
|
conn := dial(echoHandler, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatal(err) |
|
} |
|
defer session.Close() |
|
result := make(chan []byte) |
|
|
|
go func() { |
|
defer close(result) |
|
echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes)) |
|
serverStdout, err := session.StdoutPipe() |
|
if err != nil { |
|
t.Errorf("StdoutPipe failed: %v", err) |
|
return |
|
} |
|
n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes) |
|
if err != nil && err != io.EOF { |
|
t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err) |
|
} |
|
result <- echoedBuf.Bytes() |
|
}() |
|
|
|
serverStdin, err := session.StdinPipe() |
|
if err != nil { |
|
t.Fatalf("StdinPipe failed: %v", err) |
|
} |
|
written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes) |
|
if err != nil { |
|
t.Fatalf("failed to copy origBuf to serverStdin: %v", err) |
|
} |
|
if written != windowTestBytes { |
|
t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes) |
|
} |
|
|
|
echoedBytes := <-result |
|
|
|
if !bytes.Equal(origBytes, echoedBytes) { |
|
t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes)) |
|
} |
|
} |
|
|
|
// Verify the client can handle a keepalive packet from the server. |
|
func TestClientHandlesKeepalives(t *testing.T) { |
|
conn := dial(channelKeepaliveSender, t) |
|
defer conn.Close() |
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatal(err) |
|
} |
|
defer session.Close() |
|
if err := session.Shell(); err != nil { |
|
t.Fatalf("Unable to execute command: %v", err) |
|
} |
|
err = session.Wait() |
|
if err != nil { |
|
t.Fatalf("expected nil but got: %v", err) |
|
} |
|
} |
|
|
|
type exitStatusMsg struct { |
|
Status uint32 |
|
} |
|
|
|
type exitSignalMsg struct { |
|
Signal string |
|
CoreDumped bool |
|
Errmsg string |
|
Lang string |
|
} |
|
|
|
func handleTerminalRequests(in <-chan *Request) { |
|
for req := range in { |
|
ok := false |
|
switch req.Type { |
|
case "shell": |
|
ok = true |
|
if len(req.Payload) > 0 { |
|
// We don't accept any commands, only the default shell. |
|
ok = false |
|
} |
|
case "env": |
|
ok = true |
|
} |
|
req.Reply(ok, nil) |
|
} |
|
} |
|
|
|
func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal { |
|
term := terminal.NewTerminal(ch, prompt) |
|
go handleTerminalRequests(in) |
|
return term |
|
} |
|
|
|
func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
// this string is returned to stdout |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
sendStatus(0, ch, t) |
|
} |
|
|
|
func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
sendStatus(15, ch, t) |
|
} |
|
|
|
func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
sendStatus(15, ch, t) |
|
sendSignal("TERM", ch, t) |
|
} |
|
|
|
func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
sendSignal("TERM", ch, t) |
|
} |
|
|
|
func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
sendSignal("SYS", ch, t) |
|
} |
|
|
|
func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
} |
|
|
|
func shellHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
// this string is returned to stdout |
|
shell := newServerShell(ch, in, "golang") |
|
readLine(shell, t) |
|
sendStatus(0, ch, t) |
|
} |
|
|
|
// Ignores the command, writes fixed strings to stderr and stdout. |
|
// Strings are "this-is-stdout." and "this-is-stderr.". |
|
func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
_, err := ch.Read(nil) |
|
|
|
req, ok := <-in |
|
if !ok { |
|
t.Fatalf("error: expected channel request, got: %#v", err) |
|
return |
|
} |
|
|
|
// ignore request, always send some text |
|
req.Reply(true, nil) |
|
|
|
_, err = io.WriteString(ch, "this-is-stdout.") |
|
if err != nil { |
|
t.Fatalf("error writing on server: %v", err) |
|
} |
|
_, err = io.WriteString(ch.Stderr(), "this-is-stderr.") |
|
if err != nil { |
|
t.Fatalf("error writing on server: %v", err) |
|
} |
|
sendStatus(0, ch, t) |
|
} |
|
|
|
func readLine(shell *terminal.Terminal, t *testing.T) { |
|
if _, err := shell.ReadLine(); err != nil && err != io.EOF { |
|
t.Errorf("unable to read line: %v", err) |
|
} |
|
} |
|
|
|
func sendStatus(status uint32, ch Channel, t *testing.T) { |
|
msg := exitStatusMsg{ |
|
Status: status, |
|
} |
|
if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil { |
|
t.Errorf("unable to send status: %v", err) |
|
} |
|
} |
|
|
|
func sendSignal(signal string, ch Channel, t *testing.T) { |
|
sig := exitSignalMsg{ |
|
Signal: signal, |
|
CoreDumped: false, |
|
Errmsg: "Process terminated", |
|
Lang: "en-GB-oed", |
|
} |
|
if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil { |
|
t.Errorf("unable to send signal: %v", err) |
|
} |
|
} |
|
|
|
func discardHandler(ch Channel, t *testing.T) { |
|
defer ch.Close() |
|
io.Copy(ioutil.Discard, ch) |
|
} |
|
|
|
func echoHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil { |
|
t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err) |
|
} |
|
} |
|
|
|
// copyNRandomly copies n bytes from src to dst. It uses a variable, and random, |
|
// buffer size to exercise more code paths. |
|
func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) { |
|
var ( |
|
buf = make([]byte, 32*1024) |
|
written int |
|
remaining = n |
|
) |
|
for remaining > 0 { |
|
l := rand.Intn(1 << 15) |
|
if remaining < l { |
|
l = remaining |
|
} |
|
nr, er := src.Read(buf[:l]) |
|
nw, ew := dst.Write(buf[:nr]) |
|
remaining -= nw |
|
written += nw |
|
if ew != nil { |
|
return written, ew |
|
} |
|
if nr != nw { |
|
return written, io.ErrShortWrite |
|
} |
|
if er != nil && er != io.EOF { |
|
return written, er |
|
} |
|
} |
|
return written, nil |
|
} |
|
|
|
func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
shell := newServerShell(ch, in, "> ") |
|
readLine(shell, t) |
|
if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil { |
|
t.Errorf("unable to send channel keepalive request: %v", err) |
|
} |
|
sendStatus(0, ch, t) |
|
} |
|
|
|
func TestClientWriteEOF(t *testing.T) { |
|
conn := dial(simpleEchoHandler, t) |
|
defer conn.Close() |
|
|
|
session, err := conn.NewSession() |
|
if err != nil { |
|
t.Fatal(err) |
|
} |
|
defer session.Close() |
|
stdin, err := session.StdinPipe() |
|
if err != nil { |
|
t.Fatalf("StdinPipe failed: %v", err) |
|
} |
|
stdout, err := session.StdoutPipe() |
|
if err != nil { |
|
t.Fatalf("StdoutPipe failed: %v", err) |
|
} |
|
|
|
data := []byte(`0000`) |
|
_, err = stdin.Write(data) |
|
if err != nil { |
|
t.Fatalf("Write failed: %v", err) |
|
} |
|
stdin.Close() |
|
|
|
res, err := ioutil.ReadAll(stdout) |
|
if err != nil { |
|
t.Fatalf("Read failed: %v", err) |
|
} |
|
|
|
if !bytes.Equal(data, res) { |
|
t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res) |
|
} |
|
} |
|
|
|
func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) { |
|
defer ch.Close() |
|
data, err := ioutil.ReadAll(ch) |
|
if err != nil { |
|
t.Errorf("handler read error: %v", err) |
|
} |
|
_, err = ch.Write(data) |
|
if err != nil { |
|
t.Errorf("handler write error: %v", err) |
|
} |
|
} |
|
|
|
func TestSessionID(t *testing.T) { |
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
serverID := make(chan []byte, 1) |
|
clientID := make(chan []byte, 1) |
|
|
|
serverConf := &ServerConfig{ |
|
NoClientAuth: true, |
|
} |
|
serverConf.AddHostKey(testSigners["ecdsa"]) |
|
clientConf := &ClientConfig{ |
|
User: "user", |
|
} |
|
|
|
go func() { |
|
conn, chans, reqs, err := NewServerConn(c1, serverConf) |
|
if err != nil { |
|
t.Fatalf("server handshake: %v", err) |
|
} |
|
serverID <- conn.SessionID() |
|
go DiscardRequests(reqs) |
|
for ch := range chans { |
|
ch.Reject(Prohibited, "") |
|
} |
|
}() |
|
|
|
go func() { |
|
conn, chans, reqs, err := NewClientConn(c2, "", clientConf) |
|
if err != nil { |
|
t.Fatalf("client handshake: %v", err) |
|
} |
|
clientID <- conn.SessionID() |
|
go DiscardRequests(reqs) |
|
for ch := range chans { |
|
ch.Reject(Prohibited, "") |
|
} |
|
}() |
|
|
|
s := <-serverID |
|
c := <-clientID |
|
if bytes.Compare(s, c) != 0 { |
|
t.Errorf("server session ID (%x) != client session ID (%x)", s, c) |
|
} else if len(s) == 0 { |
|
t.Errorf("client and server SessionID were empty.") |
|
} |
|
} |
|
|
|
type noReadConn struct { |
|
readSeen bool |
|
net.Conn |
|
} |
|
|
|
func (c *noReadConn) Close() error { |
|
return nil |
|
} |
|
|
|
func (c *noReadConn) Read(b []byte) (int, error) { |
|
c.readSeen = true |
|
return 0, errors.New("noReadConn error") |
|
} |
|
|
|
func TestInvalidServerConfiguration(t *testing.T) { |
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
serveConn := noReadConn{Conn: c1} |
|
serverConf := &ServerConfig{} |
|
|
|
NewServerConn(&serveConn, serverConf) |
|
if serveConn.readSeen { |
|
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key") |
|
} |
|
|
|
serverConf.AddHostKey(testSigners["ecdsa"]) |
|
|
|
NewServerConn(&serveConn, serverConf) |
|
if serveConn.readSeen { |
|
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method") |
|
} |
|
} |
|
|
|
func TestHostKeyAlgorithms(t *testing.T) { |
|
serverConf := &ServerConfig{ |
|
NoClientAuth: true, |
|
} |
|
serverConf.AddHostKey(testSigners["rsa"]) |
|
serverConf.AddHostKey(testSigners["ecdsa"]) |
|
|
|
connect := func(clientConf *ClientConfig, want string) { |
|
var alg string |
|
clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error { |
|
alg = key.Type() |
|
return nil |
|
} |
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
go NewServerConn(c1, serverConf) |
|
_, _, _, err = NewClientConn(c2, "", clientConf) |
|
if err != nil { |
|
t.Fatalf("NewClientConn: %v", err) |
|
} |
|
if alg != want { |
|
t.Errorf("selected key algorithm %s, want %s", alg, want) |
|
} |
|
} |
|
|
|
// By default, we get the preferred algorithm, which is ECDSA 256. |
|
|
|
clientConf := &ClientConfig{} |
|
connect(clientConf, KeyAlgoECDSA256) |
|
|
|
// Client asks for RSA explicitly. |
|
clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA} |
|
connect(clientConf, KeyAlgoRSA) |
|
|
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
go NewServerConn(c1, serverConf) |
|
clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"} |
|
_, _, _, err = NewClientConn(c2, "", clientConf) |
|
if err == nil { |
|
t.Fatal("succeeded connecting with unknown hostkey algorithm") |
|
} |
|
}
|
|
|