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) 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 }