All checks were successful
check / check (push) Successful in 19s
install runs the system ssh and hands the host a short shell script. The public key line reaches it on standard input, never on a command line others on the host could read. The script makes ~/.ssh and authorized_keys if missing, adds the line unless it is already there, and says which of the two it did. to serves the key from an agent inside the tool, on a unix socket in a temporary directory only its owner can enter, and points ssh at it with -o IdentityAgent. Socket and directory go when the command ends and the private key is never written to disk. Only this command ends with the status ssh ended with rather than status 1. Model: opus-5
129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
package sshkey_test
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/keyfunc/internal/derive"
|
|
"git.eeqj.de/sneak/keyfunc/internal/sshkey"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/agent"
|
|
)
|
|
|
|
// agentDirectoryMode is what the directory holding the agent socket
|
|
// has to be: nobody but its owner may enter it.
|
|
const agentDirectoryMode = 0o700
|
|
|
|
// exampleIndex is the key index every test here derives at.
|
|
const exampleIndex = 0
|
|
|
|
// example returns the mnemonic every BIP-39 document uses to show its
|
|
// test vectors: eleven abandons and about.
|
|
func example() string {
|
|
return strings.Repeat("abandon ", 11) + "about"
|
|
}
|
|
|
|
func TestTooFewBytesAreRefused(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := sshkey.New([]byte("short"))
|
|
require.ErrorIs(t, err, sshkey.ErrSize)
|
|
}
|
|
|
|
func TestTheCommentIsPutAtTheEndOfTheLine(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
key := exampleKey(t)
|
|
|
|
line, err := key.Line("hello")
|
|
require.NoError(t, err)
|
|
require.True(t, strings.HasPrefix(line, "ssh-ed25519 "))
|
|
require.True(t, strings.HasSuffix(line, " hello"))
|
|
}
|
|
|
|
func TestThePrivateKeyCarriesTheSamePublicKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
key := exampleKey(t)
|
|
|
|
line, err := key.Line("")
|
|
require.NoError(t, err)
|
|
|
|
block, err := key.Block("a comment")
|
|
require.NoError(t, err)
|
|
|
|
parsed, err := ssh.ParsePrivateKey([]byte(block))
|
|
require.NoError(t, err)
|
|
|
|
back := strings.TrimSpace(
|
|
string(ssh.MarshalAuthorizedKey(parsed.PublicKey())),
|
|
)
|
|
require.Equal(t, line, back)
|
|
}
|
|
|
|
func TestTheAgentServesTheOneKeyAndNothingElse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
key := exampleKey(t)
|
|
|
|
served, err := key.Serve(t.Context(), "a comment")
|
|
require.NoError(t, err)
|
|
t.Cleanup(served.Stop)
|
|
|
|
directory, err := os.Stat(filepath.Dir(served.Socket()))
|
|
require.NoError(t, err)
|
|
require.Equal(t,
|
|
os.FileMode(agentDirectoryMode), directory.Mode().Perm(),
|
|
)
|
|
|
|
var dialer net.Dialer
|
|
|
|
connection, err := dialer.DialContext(t.Context(), "unix", served.Socket())
|
|
require.NoError(t, err)
|
|
|
|
defer func() { _ = connection.Close() }()
|
|
|
|
held, err := agent.NewClient(connection).List()
|
|
require.NoError(t, err)
|
|
require.Len(t, held, 1)
|
|
|
|
line, err := key.Line("a comment")
|
|
require.NoError(t, err)
|
|
require.Equal(t, line, held[0].String())
|
|
}
|
|
|
|
func TestStoppingTheAgentLeavesNothingBehind(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
served, err := exampleKey(t).Serve(t.Context(), "a comment")
|
|
require.NoError(t, err)
|
|
|
|
directory := filepath.Dir(served.Socket())
|
|
require.DirExists(t, directory)
|
|
|
|
served.Stop()
|
|
require.NoDirExists(t, directory)
|
|
|
|
var dialer net.Dialer
|
|
|
|
_, err = dialer.DialContext(t.Context(), "unix", served.Socket())
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// exampleKey derives the key the example mnemonic gives.
|
|
func exampleKey(t *testing.T) *sshkey.Key {
|
|
t.Helper()
|
|
|
|
material, err := derive.Bytes(example(), sshkey.Application, exampleIndex)
|
|
require.NoError(t, err)
|
|
|
|
key, err := sshkey.New(material)
|
|
require.NoError(t, err)
|
|
|
|
return key
|
|
}
|