Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh and ssh-add on Windows can get keys from pipe #14

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions example/pageant_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// based on [pageant](https://github.com/kbolino/pageant) of Kristian Bolino
package main

import (
"log"
"os"

sshagent "github.com/xanzy/ssh-agent"
"golang.org/x/crypto/ssh"
)

// This example requires all of the following to work:
// - environment variable PAGEANT_TEST_SSH_ADDR is set to a valid SSH
// server address (host:port)
// - environment variable PAGEANT_TEST_SSH_USER is set to a user name
// that the SSH server recognizes
// - Pageant is running on the local machine
// - Pageant has a key that is authorized for the user on the server
func main() {
sshAgent, pageantConn, err := sshagent.New()
if err != nil {
log.Fatalf("error on New: %s", err)
}
defer pageantConn.Close()
keys, err := sshAgent.List()
if err != nil {
log.Fatalf("error on agent.List: %s", err)
}
if len(keys) == 0 {
log.Fatalf("no keys listed by Pagent")
}
for i, key := range keys {
log.Printf("key %d: %s %s\n", i, key.Comment, ssh.FingerprintSHA256(key))
}

signers, err := sshAgent.Signers()
if err != nil {
log.Fatalf("cannot obtain signers from SSH agent: %s", err)
}
sshUser := os.Getenv("PAGEANT_TEST_SSH_USER")
config := ssh.ClientConfig{
Auth: []ssh.AuthMethod{ssh.PublicKeys(signers...)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
User: sshUser,
}
sshAddr := os.Getenv("PAGEANT_TEST_SSH_ADDR")
sshConn, err := ssh.Dial("tcp", sshAddr, &config)
if err != nil {
log.Fatalf("failed to connect to %s@%s due to error: %s", sshUser, sshAddr, err)
}
sshConn.Close()
}
4 changes: 1 addition & 3 deletions pageant_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
"golang.org/x/sys/windows"
)

// Maximum size of message can be sent to pageant
// Maximum size of message can be sent to pageant.
const MaxMessageLen = 8192

var (
Expand Down Expand Up @@ -76,8 +76,6 @@ func winAPI(dll *windows.LazyDLL, funcName string) func(...uintptr) (uintptr, ui
}

// Query sends message msg to Pageant and returns response or error.
// 'msg' is raw agent request with length prefix
// Response is raw agent response with length prefix
func query(msg []byte) ([]byte, error) {
if len(msg) > MaxMessageLen {
return nil, ErrMessageTooLong
Expand Down
4 changes: 2 additions & 2 deletions sshagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"golang.org/x/crypto/ssh/agent"
)

// New returns a new agent.Agent that uses a unix socket
// New returns a new agent.Agent that uses a unix socket.
func New() (agent.Agent, net.Conn, error) {
if !Available() {
return nil, nil, errors.New("SSH agent requested but SSH_AUTH_SOCK not-specified")
Expand All @@ -44,7 +44,7 @@ func New() (agent.Agent, net.Conn, error) {
return agent.NewClient(conn), conn, nil
}

// Available returns true is a auth socket is defined
// Available returns true if an auth socket is defined.
func Available() bool {
return os.Getenv("SSH_AUTH_SOCK") != ""
}
55 changes: 48 additions & 7 deletions sshagent_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,29 @@ import (
"errors"
"io"
"net"
"os"
"strings"
"sync"
"time"

"github.com/Microsoft/go-winio"
"golang.org/x/crypto/ssh/agent"
)

const (
sshAgentPipe = `\\.\pipe\openssh-ssh-agent`
pipe = `\\.\pipe\`
openSSHAgentPipe = pipe + "openssh-ssh-agent"
)

// Available returns true if Pageant is running
// Available returns true if Pageant is running.
func Available() bool {
if pageantWindow() != 0 {
return true
}
conn, err := winio.DialPipe(sshAgentPipe, nil)
if sshAuthSock := os.Getenv("SSH_AUTH_SOCK"); sshAuthSock != "" {
return true
}
conn, err := winio.DialPipe(openSSHAgentPipe, nil)
if err != nil {
return false
}
Expand All @@ -50,29 +57,46 @@ func Available() bool {
}

// New returns a new agent.Agent and the (custom) connection it uses
// to communicate with a running pagent.exe instance (see README.md)
// to communicate with a running pagent.exe instance (see README.md).
func New() (agent.Agent, net.Conn, error) {
if pageantWindow() != 0 {
return agent.NewClient(&conn{}), nil, nil
return agent.NewClient(&conn{}), &conn{}, nil
}

sshAgentPipe := openSSHAgentPipe
if sshAuthSock := os.Getenv("SSH_AUTH_SOCK"); sshAuthSock != "" {
conn, err := net.Dial("unix", sshAuthSock)
if err == nil {
return agent.NewClient(conn), conn, nil
}

if !strings.HasPrefix(sshAuthSock, pipe) {
sshAuthSock = pipe + sshAuthSock
}

sshAgentPipe = sshAuthSock
}

conn, err := winio.DialPipe(sshAgentPipe, nil)
if err != nil {
return nil, nil, errors.New(
"SSH agent requested, but could not detect Pageant or Windows native SSH agent",
)
}
return agent.NewClient(conn), nil, nil

return agent.NewClient(conn), conn, nil
}

type conn struct {
sync.Mutex
buf []byte
}

func (c *conn) Close() {
func (c *conn) Close() error {
c.Lock()
defer c.Unlock()
c.buf = nil
return nil
}

func (c *conn) Write(p []byte) (int, error) {
Expand Down Expand Up @@ -102,3 +126,20 @@ func (c *conn) Read(p []byte) (int, error) {

return n, nil
}

// for similarity with net.Conn
func (c *conn) LocalAddr() net.Addr {
return nil
}
func (c *conn) RemoteAddr() net.Addr {
return nil
}
func (c *conn) SetDeadline(_ time.Time) error {
return nil
}
func (c *conn) SetReadDeadline(_ time.Time) error {
return nil
}
func (c *conn) SetWriteDeadline(_ time.Time) error {
return nil
}
40 changes: 40 additions & 0 deletions sshagent_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// based on [pageant](https://github.com/kbolino/pageant) of Kristian Bolino

package sshagent

import (
"testing"
)

// Pageant must be running for this test to work.
func TestNew(t *testing.T) {
_, conn, err := New()
if err != nil {
t.Fatalf("error on New: %s", err)
} else if conn == nil {
t.Fatalf("New returned nil")
}
err = conn.Close()
if err != nil {
t.Fatalf("error on Conn.Close: %s", err)
}
}

// Pageant must be running and have at least 1 key loaded for this test to work.
func TestSSHAgentList(t *testing.T) {
sshAgent, conn, err := New()
if err != nil {
t.Fatalf("error on New: %s", err)
}
defer conn.Close()
keys, err := sshAgent.List()
if err != nil {
t.Fatalf("error on agent.List: %s", err)
}
if len(keys) == 0 {
t.Fatalf("no keys listed by Pagent")
}
for i, key := range keys {
t.Logf("key %d: %s", i, key.Comment)
}
}