aboutsummaryrefslogtreecommitdiff
path: root/rsh/sshserver_test.go
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-16 00:00:00 +0000
committerLena <lena@omega>2026-08-16 00:00:00 +0000
commitbeaa0c970d6c3538119b8a34a3f2f754b45e54cf (patch)
tree0a4be0a63a5c5b40f3db66a7e2b6c603e60d8473 /rsh/sshserver_test.go
parent0890d139e5fa501561e469aca7854791a936fcb6 (diff)
downloadrsend-beaa0c970d6c3538119b8a34a3f2f754b45e54cf.tar.gz
native: harden rsync and SSH transport
Update rsync to 3.5.0 and Go to 1.26.6. Bound SSH handshakes, pin host-key types, and build 16 KB-aligned hardened executables.
Diffstat (limited to 'rsh/sshserver_test.go')
-rw-r--r--rsh/sshserver_test.go42
1 files changed, 34 insertions, 8 deletions
diff --git a/rsh/sshserver_test.go b/rsh/sshserver_test.go
index 087bff4..f238e0b 100644
--- a/rsh/sshserver_test.go
+++ b/rsh/sshserver_test.go
@@ -1,7 +1,9 @@
package main
import (
+ "crypto/ecdsa"
"crypto/ed25519"
+ "crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
@@ -20,14 +22,24 @@ type execHandler func(cmd string, stdin io.Reader, stdout, stderr io.Writer) int
// testServer is a minimal in-process SSH server for tests: one host key, one
// accepted client public key, and a pluggable exec handler. No sshd needed.
type testServer struct {
- ln net.Listener
- hostKey ssh.Signer
- authPub ssh.PublicKey
- handle execHandler
+ ln net.Listener
+ hostKey ssh.Signer
+ extraKey ssh.Signer
+ authPub ssh.PublicKey
+ handle execHandler
}
func newTestServer(t *testing.T, clientPub ssh.PublicKey, handle execHandler) *testServer {
t.Helper()
+ return newTestServerKeys(t, clientPub, handle, false)
+}
+
+// newTestServerKeys builds the server with an ed25519 host key and, when
+// alsoECDSA is set, an ECDSA one too. A server offering both is the stock sshd
+// case: x/crypto's default client preference would pick the ECDSA key, so this
+// is what proves rsh's host-key algorithm policy is doing its job.
+func newTestServerKeys(t *testing.T, clientPub ssh.PublicKey, handle execHandler, alsoECDSA bool) *testServer {
+ t.Helper()
_, hpriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
@@ -36,11 +48,21 @@ func newTestServer(t *testing.T, clientPub ssh.PublicKey, handle execHandler) *t
if err != nil {
t.Fatal(err)
}
+ var extra ssh.Signer
+ if alsoECDSA {
+ epriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if extra, err = ssh.NewSignerFromKey(epriv); err != nil {
+ t.Fatal(err)
+ }
+ }
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
- s := &testServer{ln: ln, hostKey: signer, authPub: clientPub, handle: handle}
+ s := &testServer{ln: ln, hostKey: signer, extraKey: extra, authPub: clientPub, handle: handle}
go s.serve()
t.Cleanup(func() { ln.Close() })
return s
@@ -58,6 +80,9 @@ func (s *testServer) serve() {
},
}
cfg.AddHostKey(s.hostKey)
+ if s.extraKey != nil {
+ cfg.AddHostKey(s.extraKey)
+ }
for {
nConn, err := s.ln.Accept()
if err != nil {
@@ -107,15 +132,16 @@ func (s *testServer) handleSession(ch ssh.Channel, requests <-chan *ssh.Request)
}
}
-// shellExec runs cmd via /bin/sh -c, exactly as sshd would, wiring the channel
-// to the process. Used by the real-rsync end-to-end test.
+// shellExecIn runs cmd via /bin/sh -c, exactly as sshd would, wiring the
+// channel to the process. Used by the real-rsync end-to-end test.
//
// stdin is fed through a detached goroutine rather than c.Stdin so that Wait
// does not block on the stdin copy: rsync's remote receiver exits while the
// client still holds the channel open, and a c.Run with a non-file Stdin would
// deadlock waiting for that copy to finish.
-func shellExec(cmd string, stdin io.Reader, stdout, stderr io.Writer) int {
+func shellExecIn(dir, cmd string, stdin io.Reader, stdout, stderr io.Writer) int {
c := exec.Command("/bin/sh", "-c", cmd)
+ c.Dir = dir
c.Stdout = stdout
c.Stderr = stderr
inPipe, err := c.StdinPipe()