mirror of
				https://github.com/appleboy/drone-ssh.git
				synced 2025-10-29 00:51:15 +08:00 
			
		
		
		
	feat: Add time out flag.
This commit is contained in:
		
							parent
							
								
									28ffc3a790
								
							
						
					
					
						commit
						8b10b8c3e9
					
				
							
								
								
									
										19
									
								
								DOCS.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								DOCS.md
									
									
									
									
									
								
							| @ -72,6 +72,22 @@ pipeline: | |||||||
|       - echo world |       - echo world | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | Example configuration for command timeout (unit: second), default value is 60 seconds: | ||||||
|  | 
 | ||||||
|  | ```diff | ||||||
|  | pipeline: | ||||||
|  |   ssh: | ||||||
|  |     image: appleboy/drone-ssh | ||||||
|  |     host: foo.com | ||||||
|  |     username: root | ||||||
|  |     password: 1234 | ||||||
|  |     port: 22 | ||||||
|  | +   command_timeout: 10 | ||||||
|  |     script: | ||||||
|  |       - echo hello | ||||||
|  |       - echo world | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| Example configuration for success build: | Example configuration for success build: | ||||||
| 
 | 
 | ||||||
| ```diff | ```diff | ||||||
| @ -132,3 +148,6 @@ script | |||||||
| 
 | 
 | ||||||
| timeout | timeout | ||||||
| : Timeout is the maximum amount of time for the TCP connection to establish. | : Timeout is the maximum amount of time for the TCP connection to establish. | ||||||
|  | 
 | ||||||
|  | command_timeout | ||||||
|  | : Command timeout is the maximum amount of time for the execute commands, default is 60 secs. | ||||||
|  | |||||||
| @ -1,146 +0,0 @@ | |||||||
| // Package easyssh provides a simple implementation of some SSH protocol
 |  | ||||||
| // features in Go. You can simply run a command on a remote server or get a file
 |  | ||||||
| // even simpler than native console SSH client. You don't need to think about
 |  | ||||||
| // Dials, sessions, defers, or public keys... Let easyssh think about it!
 |  | ||||||
| package easyssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // MakeConfig Contains main authority information.
 |  | ||||||
| // User field should be a name of user on remote server (ex. john in ssh john@example.com).
 |  | ||||||
| // Server field should be a remote machine address (ex. example.com in ssh john@example.com)
 |  | ||||||
| // Key is a path to private key on your local machine.
 |  | ||||||
| // Port is SSH server port on remote machine.
 |  | ||||||
| // Note: easyssh looking for private key in user's home directory (ex. /home/john + Key).
 |  | ||||||
| // Then ensure your Key begins from '/' (ex. /.ssh/id_rsa)
 |  | ||||||
| type MakeConfig struct { |  | ||||||
| 	User     string |  | ||||||
| 	Server   string |  | ||||||
| 	Key      string |  | ||||||
| 	KeyPath  string |  | ||||||
| 	Port     string |  | ||||||
| 	Password string |  | ||||||
| 	Timeout  time.Duration |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // returns ssh.Signer from user you running app home path + cutted key path.
 |  | ||||||
| // (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") )
 |  | ||||||
| func getKeyFile(keypath string) (ssh.Signer, error) { |  | ||||||
| 	buf, err := ioutil.ReadFile(keypath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pubkey, err := ssh.ParsePrivateKey(buf) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return pubkey, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // connects to remote server using MakeConfig struct and returns *ssh.Session
 |  | ||||||
| func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { |  | ||||||
| 	// auths holds the detected ssh auth methods
 |  | ||||||
| 	auths := []ssh.AuthMethod{} |  | ||||||
| 
 |  | ||||||
| 	// figure out what auths are requested, what is supported
 |  | ||||||
| 	if ssh_conf.Password != "" { |  | ||||||
| 		auths = append(auths, ssh.Password(ssh_conf.Password)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if ssh_conf.KeyPath != "" { |  | ||||||
| 		if pubkey, err := getKeyFile(ssh_conf.KeyPath); err == nil { |  | ||||||
| 			auths = append(auths, ssh.PublicKeys(pubkey)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if ssh_conf.Key != "" { |  | ||||||
| 		signer, _ := ssh.ParsePrivateKey([]byte(ssh_conf.Key)) |  | ||||||
| 		auths = append(auths, ssh.PublicKeys(signer)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	config := &ssh.ClientConfig{ |  | ||||||
| 		Timeout: ssh_conf.Timeout, |  | ||||||
| 		User:    ssh_conf.User, |  | ||||||
| 		Auth:    auths, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	client, err := ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	session, err := client.NewSession() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return session, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Stream returns one channel that combines the stdout and stderr of the command
 |  | ||||||
| // as it is run on the remote machine, and another that sends true when the
 |  | ||||||
| // command is done. The sessions and channels will then be closed.
 |  | ||||||
| func (ssh_conf *MakeConfig) Stream(command string) (output chan string, done chan bool, err error) { |  | ||||||
| 	// connect to remote host
 |  | ||||||
| 	session, err := ssh_conf.connect() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return output, done, err |  | ||||||
| 	} |  | ||||||
| 	// connect to both outputs (they are of type io.Reader)
 |  | ||||||
| 	outReader, err := session.StdoutPipe() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return output, done, err |  | ||||||
| 	} |  | ||||||
| 	errReader, err := session.StderrPipe() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return output, done, err |  | ||||||
| 	} |  | ||||||
| 	// combine outputs, create a line-by-line scanner
 |  | ||||||
| 	outputReader := io.MultiReader(outReader, errReader) |  | ||||||
| 	err = session.Start(command) |  | ||||||
| 	scanner := bufio.NewScanner(outputReader) |  | ||||||
| 	// continuously send the command's output over the channel
 |  | ||||||
| 	outputChan := make(chan string) |  | ||||||
| 	done = make(chan bool) |  | ||||||
| 	go func(scanner *bufio.Scanner, out chan string, done chan bool) { |  | ||||||
| 		defer close(outputChan) |  | ||||||
| 		defer close(done) |  | ||||||
| 		for scanner.Scan() { |  | ||||||
| 			outputChan <- scanner.Text() |  | ||||||
| 		} |  | ||||||
| 		// close all of our open resources
 |  | ||||||
| 		done <- true |  | ||||||
| 		session.Close() |  | ||||||
| 	}(scanner, outputChan, done) |  | ||||||
| 	return outputChan, done, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Run command on remote machine and returns its stdout as a string
 |  | ||||||
| func (ssh_conf *MakeConfig) Run(command string) (outStr string, err error) { |  | ||||||
| 	outChan, doneChan, err := ssh_conf.Stream(command) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return outStr, err |  | ||||||
| 	} |  | ||||||
| 	// read from the output channel until the done signal is passed
 |  | ||||||
| 	stillGoing := true |  | ||||||
| 	for stillGoing { |  | ||||||
| 		select { |  | ||||||
| 		case <-doneChan: |  | ||||||
| 			stillGoing = false |  | ||||||
| 		case line := <-outChan: |  | ||||||
| 			outStr += line + "\n" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// return the concatenation of all signals from the output channel
 |  | ||||||
| 	return outStr, err |  | ||||||
| } |  | ||||||
							
								
								
									
										23
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								main.go
									
									
									
									
									
								
							| @ -62,6 +62,12 @@ func main() { | |||||||
| 			Usage:  "connection timeout", | 			Usage:  "connection timeout", | ||||||
| 			EnvVar: "PLUGIN_TIMEOUT,SSH_TIMEOUT", | 			EnvVar: "PLUGIN_TIMEOUT,SSH_TIMEOUT", | ||||||
| 		}, | 		}, | ||||||
|  | 		cli.IntFlag{ | ||||||
|  | 			Name:   "command.timeout,T", | ||||||
|  | 			Usage:  "command timeout", | ||||||
|  | 			EnvVar: "PLUGIN_COMMAND_TIMEOUT,SSH_COMMAND_TIMEOUT", | ||||||
|  | 			Value:  60, | ||||||
|  | 		}, | ||||||
| 		cli.StringSliceFlag{ | 		cli.StringSliceFlag{ | ||||||
| 			Name:   "script,s", | 			Name:   "script,s", | ||||||
| 			Usage:  "execute commands", | 			Usage:  "execute commands", | ||||||
| @ -116,14 +122,15 @@ func run(c *cli.Context) error { | |||||||
| 
 | 
 | ||||||
| 	plugin := Plugin{ | 	plugin := Plugin{ | ||||||
| 		Config: Config{ | 		Config: Config{ | ||||||
| 			Key:      c.String("ssh-key"), | 			Key:            c.String("ssh-key"), | ||||||
| 			KeyPath:  c.String("key-path"), | 			KeyPath:        c.String("key-path"), | ||||||
| 			UserName: c.String("user"), | 			UserName:       c.String("user"), | ||||||
| 			Password: c.String("password"), | 			Password:       c.String("password"), | ||||||
| 			Host:     c.StringSlice("host"), | 			Host:           c.StringSlice("host"), | ||||||
| 			Port:     c.Int("port"), | 			Port:           c.Int("port"), | ||||||
| 			Timeout:  c.Duration("timeout"), | 			Timeout:        c.Duration("timeout"), | ||||||
| 			Script:   c.StringSlice("script"), | 			CommandTimeout: c.Int("command.timeout"), | ||||||
|  | 			Script:         c.StringSlice("script"), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								plugin.go
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								plugin.go
									
									
									
									
									
								
							| @ -8,7 +8,7 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/appleboy/drone-ssh/easyssh" | 	"github.com/appleboy/easyssh-proxy" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var wg sync.WaitGroup | var wg sync.WaitGroup | ||||||
| @ -21,14 +21,15 @@ const ( | |||||||
| type ( | type ( | ||||||
| 	// Config for the plugin.
 | 	// Config for the plugin.
 | ||||||
| 	Config struct { | 	Config struct { | ||||||
| 		Key      string | 		Key            string | ||||||
| 		KeyPath  string | 		KeyPath        string | ||||||
| 		UserName string | 		UserName       string | ||||||
| 		Password string | 		Password       string | ||||||
| 		Host     []string | 		Host           []string | ||||||
| 		Port     int | 		Port           int | ||||||
| 		Timeout  time.Duration | 		Timeout        time.Duration | ||||||
| 		Script   []string | 		CommandTimeout int | ||||||
|  | 		Script         []string | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Plugin structure
 | 	// Plugin structure
 | ||||||
| @ -68,8 +69,12 @@ func (p Plugin) Exec() error { | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			p.log(host, "commands: ", strings.Join(p.Config.Script, "\n")) | 			p.log(host, "commands: ", strings.Join(p.Config.Script, "\n")) | ||||||
| 			response, err := ssh.Run(strings.Join(p.Config.Script, "\n")) | 			outStr, errStr, isTimeout, err := ssh.Run(strings.Join(p.Config.Script, "\n"), p.Config.CommandTimeout) | ||||||
| 			p.log(host, "outputs:", response) | 			p.log(host, "outputs:", outStr) | ||||||
|  | 
 | ||||||
|  | 			if !isTimeout || len(errStr) != 0 { | ||||||
|  | 				errChannel <- fmt.Errorf(errStr) | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				errChannel <- err | 				errChannel <- err | ||||||
|  | |||||||
| @ -100,3 +100,35 @@ func TestSSHScriptFromKeyFile(t *testing.T) { | |||||||
| 	err := plugin.Exec() | 	err := plugin.Exec() | ||||||
| 	assert.Nil(t, err) | 	assert.Nil(t, err) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestSSHCommandTimeOut(t *testing.T) { | ||||||
|  | 	plugin := Plugin{ | ||||||
|  | 		Config: Config{ | ||||||
|  | 			Host:           []string{"localhost"}, | ||||||
|  | 			UserName:       "drone-scp", | ||||||
|  | 			Port:           22, | ||||||
|  | 			KeyPath:        "./tests/.ssh/id_rsa", | ||||||
|  | 			Script:         []string{"sleep 5"}, | ||||||
|  | 			CommandTimeout: 1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := plugin.Exec() | ||||||
|  | 	assert.NotNil(t, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestSSHCommandNotFound(t *testing.T) { | ||||||
|  | 	plugin := Plugin{ | ||||||
|  | 		Config: Config{ | ||||||
|  | 			Host:           []string{"localhost"}, | ||||||
|  | 			UserName:       "drone-scp", | ||||||
|  | 			Port:           22, | ||||||
|  | 			KeyPath:        "./tests/.ssh/id_rsa", | ||||||
|  | 			Script:         []string{"whoami1234"}, | ||||||
|  | 			CommandTimeout: 1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := plugin.Exec() | ||||||
|  | 	assert.NotNil(t, err) | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								vendor/github.com/appleboy/easyssh-proxy/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/appleboy/easyssh-proxy/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2017 Bo-Yi Wu | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										60
									
								
								vendor/github.com/appleboy/easyssh-proxy/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								vendor/github.com/appleboy/easyssh-proxy/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | .PHONY: test drone-ssh build fmt vet errcheck lint install update release-dirs release-build release-copy release-check release coverage | ||||||
|  | 
 | ||||||
|  | PACKAGES ?= $(shell go list ./... | grep -v /vendor/) | ||||||
|  | 
 | ||||||
|  | all: build | ||||||
|  | 
 | ||||||
|  | fmt: | ||||||
|  | 	find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w | ||||||
|  | 
 | ||||||
|  | vet: | ||||||
|  | 	go vet $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | errcheck: | ||||||
|  | 	@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | ||||||
|  | 		go get -u github.com/kisielk/errcheck; \
 | ||||||
|  | 	fi | ||||||
|  | 	errcheck $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | lint: | ||||||
|  | 	@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | ||||||
|  | 		go get -u github.com/golang/lint/golint; \
 | ||||||
|  | 	fi | ||||||
|  | 	for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; | ||||||
|  | 
 | ||||||
|  | unconvert: | ||||||
|  | 	@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | ||||||
|  | 		go get -u github.com/mdempsky/unconvert; \
 | ||||||
|  | 	fi | ||||||
|  | 	for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done; | ||||||
|  | 
 | ||||||
|  | test: | ||||||
|  | 	for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done; | ||||||
|  | 
 | ||||||
|  | html: | ||||||
|  | 	go tool cover -html=coverage.txt | ||||||
|  | 
 | ||||||
|  | coverage: | ||||||
|  | 	sed -i '/main.go/d' .cover/coverage.txt | ||||||
|  | 	curl -s https://codecov.io/bash > .codecov && \
 | ||||||
|  | 	chmod +x .codecov && \
 | ||||||
|  | 	./.codecov -f .cover/coverage.txt | ||||||
|  | 
 | ||||||
|  | clean: | ||||||
|  | 	go clean -x -i ./... | ||||||
|  | 	rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor | ||||||
|  | 
 | ||||||
|  | ssh-server: | ||||||
|  | 	adduser -h /home/drone-scp -s /bin/bash -D -S drone-scp | ||||||
|  | 	echo drone-scp:1234 | chpasswd | ||||||
|  | 	mkdir -p /home/drone-scp/.ssh | ||||||
|  | 	chmod 700 /home/drone-scp/.ssh | ||||||
|  | 	cp tests/.ssh/id_rsa.pub /home/drone-scp/.ssh/authorized_keys | ||||||
|  | 	chown -R drone-scp /home/drone-scp/.ssh | ||||||
|  | 	# install ssh and start server | ||||||
|  | 	apk add --update openssh openrc | ||||||
|  | 	rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key | ||||||
|  | 	./tests/entrypoint.sh /usr/sbin/sshd -D & | ||||||
|  | 
 | ||||||
|  | version: | ||||||
|  | 	@echo $(VERSION) | ||||||
							
								
								
									
										5
									
								
								vendor/github.com/appleboy/easyssh-proxy/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/appleboy/easyssh-proxy/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | # easyssh-proxy | ||||||
|  | 
 | ||||||
|  | [](https://godoc.org/github.com/appleboy/easyssh-proxy) [](http://drone.wu-boy.com/appleboy/easyssh-proxy) [](https://codecov.io/gh/appleboy/easyssh-proxy) [](https://goreportcard.com/report/github.com/appleboy/easyssh-proxy) | ||||||
|  | 
 | ||||||
|  | easyssh-proxy provides a simple implementation of some SSH protocol features in Go | ||||||
							
								
								
									
										250
									
								
								vendor/github.com/appleboy/easyssh-proxy/easyssh.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								vendor/github.com/appleboy/easyssh-proxy/easyssh.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,250 @@ | |||||||
|  | // Package easyssh provides a simple implementation of some SSH protocol
 | ||||||
|  | // features in Go. You can simply run a command on a remote server or get a file
 | ||||||
|  | // even simpler than native console SSH client. You don't need to think about
 | ||||||
|  | // Dials, sessions, defers, or public keys... Let easyssh think about it!
 | ||||||
|  | package easyssh | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | 	"golang.org/x/crypto/ssh/agent" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // MakeConfig Contains main authority information.
 | ||||||
|  | // User field should be a name of user on remote server (ex. john in ssh john@example.com).
 | ||||||
|  | // Server field should be a remote machine address (ex. example.com in ssh john@example.com)
 | ||||||
|  | // Key is a path to private key on your local machine.
 | ||||||
|  | // Port is SSH server port on remote machine.
 | ||||||
|  | // Note: easyssh looking for private key in user's home directory (ex. /home/john + Key).
 | ||||||
|  | // Then ensure your Key begins from '/' (ex. /.ssh/id_rsa)
 | ||||||
|  | type MakeConfig struct { | ||||||
|  | 	User     string | ||||||
|  | 	Server   string | ||||||
|  | 	Key      string | ||||||
|  | 	KeyPath  string | ||||||
|  | 	Port     string | ||||||
|  | 	Password string | ||||||
|  | 	Timeout  time.Duration | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sshConfig struct { | ||||||
|  | 	User     string | ||||||
|  | 	Key      string | ||||||
|  | 	KeyPath  string | ||||||
|  | 	Password string | ||||||
|  | 	Timeout  time.Duration | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // returns ssh.Signer from user you running app home path + cutted key path.
 | ||||||
|  | // (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") )
 | ||||||
|  | func getKeyFile(keypath string) (ssh.Signer, error) { | ||||||
|  | 	buf, err := ioutil.ReadFile(keypath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pubkey, err := ssh.ParsePrivateKey(buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pubkey, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSSHConfig(config sshConfig) *ssh.ClientConfig { | ||||||
|  | 	// auths holds the detected ssh auth methods
 | ||||||
|  | 	auths := []ssh.AuthMethod{} | ||||||
|  | 
 | ||||||
|  | 	// figure out what auths are requested, what is supported
 | ||||||
|  | 	if config.Password != "" { | ||||||
|  | 		auths = append(auths, ssh.Password(config.Password)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { | ||||||
|  | 		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)) | ||||||
|  | 		defer sshAgent.Close() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if config.KeyPath != "" { | ||||||
|  | 		if pubkey, err := getKeyFile(config.KeyPath); err == nil { | ||||||
|  | 			auths = append(auths, ssh.PublicKeys(pubkey)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if config.Key != "" { | ||||||
|  | 		signer, _ := ssh.ParsePrivateKey([]byte(config.Key)) | ||||||
|  | 		auths = append(auths, ssh.PublicKeys(signer)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &ssh.ClientConfig{ | ||||||
|  | 		Timeout: config.Timeout, | ||||||
|  | 		User:    config.User, | ||||||
|  | 		Auth:    auths, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // connect to remote server using MakeConfig struct and returns *ssh.Session
 | ||||||
|  | func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { | ||||||
|  | 	config := getSSHConfig(sshConfig{ | ||||||
|  | 		User:     ssh_conf.User, | ||||||
|  | 		Key:      ssh_conf.Key, | ||||||
|  | 		KeyPath:  ssh_conf.KeyPath, | ||||||
|  | 		Password: ssh_conf.Password, | ||||||
|  | 		Timeout:  ssh_conf.Timeout, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	client, err := ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	session, err := client.NewSession() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return session, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stream returns one channel that combines the stdout and stderr of the command
 | ||||||
|  | // as it is run on the remote machine, and another that sends true when the
 | ||||||
|  | // command is done. The sessions and channels will then be closed.
 | ||||||
|  | func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan string, stderr chan string, done chan bool, err error) { | ||||||
|  | 	// connect to remote host
 | ||||||
|  | 	session, err := ssh_conf.connect() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stdout, stderr, done, err | ||||||
|  | 	} | ||||||
|  | 	// connect to both outputs (they are of type io.Reader)
 | ||||||
|  | 	outReader, err := session.StdoutPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stdout, stderr, done, err | ||||||
|  | 	} | ||||||
|  | 	errReader, err := session.StderrPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stdout, stderr, done, err | ||||||
|  | 	} | ||||||
|  | 	// combine outputs, create a line-by-line scanner
 | ||||||
|  | 	stdoutReader := io.MultiReader(outReader) | ||||||
|  | 	stderrReader := io.MultiReader(errReader) | ||||||
|  | 	err = session.Start(command) | ||||||
|  | 	stdoutScanner := bufio.NewScanner(stdoutReader) | ||||||
|  | 	stderrScanner := bufio.NewScanner(stderrReader) | ||||||
|  | 	// continuously send the command's output over the channel
 | ||||||
|  | 	stdoutChan := make(chan string) | ||||||
|  | 	stderrChan := make(chan string) | ||||||
|  | 	done = make(chan bool) | ||||||
|  | 
 | ||||||
|  | 	go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) { | ||||||
|  | 		defer close(stdoutChan) | ||||||
|  | 		defer close(stderrChan) | ||||||
|  | 		defer close(done) | ||||||
|  | 
 | ||||||
|  | 		timeoutChan := time.After(time.Duration(timeout) * time.Second) | ||||||
|  | 		res := make(chan bool, 1) | ||||||
|  | 
 | ||||||
|  | 		go func() { | ||||||
|  | 			for stdoutScanner.Scan() { | ||||||
|  | 				stdoutChan <- stdoutScanner.Text() | ||||||
|  | 			} | ||||||
|  | 			for stderrScanner.Scan() { | ||||||
|  | 				stderrChan <- stderrScanner.Text() | ||||||
|  | 			} | ||||||
|  | 			// close all of our open resources
 | ||||||
|  | 			res <- true | ||||||
|  | 		}() | ||||||
|  | 
 | ||||||
|  | 		select { | ||||||
|  | 		case <-res: | ||||||
|  | 			stdoutChan <- "" | ||||||
|  | 			stderrChan <- "" | ||||||
|  | 			done <- true | ||||||
|  | 		case <-timeoutChan: | ||||||
|  | 			stdoutChan <- "" | ||||||
|  | 			stderrChan <- "Run Command Timeout!" | ||||||
|  | 			done <- false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		session.Close() | ||||||
|  | 	}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done) | ||||||
|  | 	return stdoutChan, stderrChan, done, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Run command on remote machine and returns its stdout as a string
 | ||||||
|  | func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) { | ||||||
|  | 	stdoutChan, stderrChan, doneChan, err := ssh_conf.Stream(command, timeout) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return outStr, errStr, isTimeout, err | ||||||
|  | 	} | ||||||
|  | 	// read from the output channel until the done signal is passed
 | ||||||
|  | 	stillGoing := true | ||||||
|  | 	for stillGoing { | ||||||
|  | 		select { | ||||||
|  | 		case isTimeout = <-doneChan: | ||||||
|  | 			stillGoing = false | ||||||
|  | 		case outline := <-stdoutChan: | ||||||
|  | 			if outline != "" { | ||||||
|  | 				outStr += outline + "\n" | ||||||
|  | 			} | ||||||
|  | 		case errline := <-stderrChan: | ||||||
|  | 			if errline != "" { | ||||||
|  | 				errStr += errline + "\n" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// return the concatenation of all signals from the output channel
 | ||||||
|  | 	return outStr, errStr, isTimeout, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Scp uploads sourceFile to remote machine like native scp console app.
 | ||||||
|  | func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error { | ||||||
|  | 	session, err := ssh_conf.connect() | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer session.Close() | ||||||
|  | 
 | ||||||
|  | 	targetFile := filepath.Base(etargetFile) | ||||||
|  | 
 | ||||||
|  | 	src, srcErr := os.Open(sourceFile) | ||||||
|  | 
 | ||||||
|  | 	if srcErr != nil { | ||||||
|  | 		return srcErr | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	srcStat, statErr := src.Stat() | ||||||
|  | 
 | ||||||
|  | 	if statErr != nil { | ||||||
|  | 		return statErr | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		w, _ := session.StdinPipe() | ||||||
|  | 
 | ||||||
|  | 		fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile) | ||||||
|  | 
 | ||||||
|  | 		if srcStat.Size() > 0 { | ||||||
|  | 			io.Copy(w, src) | ||||||
|  | 			fmt.Fprint(w, "\x00") | ||||||
|  | 			w.Close() | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Fprint(w, "\x00") | ||||||
|  | 			w.Close() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	if err := session.Run(fmt.Sprintf("scp -tr %s", etargetFile)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										659
									
								
								vendor/golang.org/x/crypto/ssh/agent/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										659
									
								
								vendor/golang.org/x/crypto/ssh/agent/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,659 @@ | |||||||
|  | // Copyright 2012 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 agent implements the ssh-agent protocol, and provides both
 | ||||||
|  | // a client and a server. The client can talk to a standard ssh-agent
 | ||||||
|  | // that uses UNIX sockets, and one could implement an alternative
 | ||||||
|  | // ssh-agent process using the sample server.
 | ||||||
|  | //
 | ||||||
|  | // References:
 | ||||||
|  | //  [PROTOCOL.agent]:    http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
 | ||||||
|  | package agent // import "golang.org/x/crypto/ssh/agent"
 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/dsa" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ed25519" | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Agent represents the capabilities of an ssh-agent.
 | ||||||
|  | type Agent interface { | ||||||
|  | 	// List returns the identities known to the agent.
 | ||||||
|  | 	List() ([]*Key, error) | ||||||
|  | 
 | ||||||
|  | 	// Sign has the agent sign the data using a protocol 2 key as defined
 | ||||||
|  | 	// in [PROTOCOL.agent] section 2.6.2.
 | ||||||
|  | 	Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) | ||||||
|  | 
 | ||||||
|  | 	// Add adds a private key to the agent.
 | ||||||
|  | 	Add(key AddedKey) error | ||||||
|  | 
 | ||||||
|  | 	// Remove removes all identities with the given public key.
 | ||||||
|  | 	Remove(key ssh.PublicKey) error | ||||||
|  | 
 | ||||||
|  | 	// RemoveAll removes all identities.
 | ||||||
|  | 	RemoveAll() error | ||||||
|  | 
 | ||||||
|  | 	// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
 | ||||||
|  | 	Lock(passphrase []byte) error | ||||||
|  | 
 | ||||||
|  | 	// Unlock undoes the effect of Lock
 | ||||||
|  | 	Unlock(passphrase []byte) error | ||||||
|  | 
 | ||||||
|  | 	// Signers returns signers for all the known keys.
 | ||||||
|  | 	Signers() ([]ssh.Signer, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddedKey describes an SSH key to be added to an Agent.
 | ||||||
|  | type AddedKey struct { | ||||||
|  | 	// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
 | ||||||
|  | 	// *ecdsa.PrivateKey, which will be inserted into the agent.
 | ||||||
|  | 	PrivateKey interface{} | ||||||
|  | 	// Certificate, if not nil, is communicated to the agent and will be
 | ||||||
|  | 	// stored with the key.
 | ||||||
|  | 	Certificate *ssh.Certificate | ||||||
|  | 	// Comment is an optional, free-form string.
 | ||||||
|  | 	Comment string | ||||||
|  | 	// LifetimeSecs, if not zero, is the number of seconds that the
 | ||||||
|  | 	// agent will store the key for.
 | ||||||
|  | 	LifetimeSecs uint32 | ||||||
|  | 	// ConfirmBeforeUse, if true, requests that the agent confirm with the
 | ||||||
|  | 	// user before each use of this key.
 | ||||||
|  | 	ConfirmBeforeUse bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 3.
 | ||||||
|  | const ( | ||||||
|  | 	agentRequestV1Identities   = 1 | ||||||
|  | 	agentRemoveAllV1Identities = 9 | ||||||
|  | 
 | ||||||
|  | 	// 3.2 Requests from client to agent for protocol 2 key operations
 | ||||||
|  | 	agentAddIdentity         = 17 | ||||||
|  | 	agentRemoveIdentity      = 18 | ||||||
|  | 	agentRemoveAllIdentities = 19 | ||||||
|  | 	agentAddIdConstrained    = 25 | ||||||
|  | 
 | ||||||
|  | 	// 3.3 Key-type independent requests from client to agent
 | ||||||
|  | 	agentAddSmartcardKey            = 20 | ||||||
|  | 	agentRemoveSmartcardKey         = 21 | ||||||
|  | 	agentLock                       = 22 | ||||||
|  | 	agentUnlock                     = 23 | ||||||
|  | 	agentAddSmartcardKeyConstrained = 26 | ||||||
|  | 
 | ||||||
|  | 	// 3.7 Key constraint identifiers
 | ||||||
|  | 	agentConstrainLifetime = 1 | ||||||
|  | 	agentConstrainConfirm  = 2 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // maxAgentResponseBytes is the maximum agent reply size that is accepted. This
 | ||||||
|  | // is a sanity check, not a limit in the spec.
 | ||||||
|  | const maxAgentResponseBytes = 16 << 20 | ||||||
|  | 
 | ||||||
|  | // Agent messages:
 | ||||||
|  | // These structures mirror the wire format of the corresponding ssh agent
 | ||||||
|  | // messages found in [PROTOCOL.agent].
 | ||||||
|  | 
 | ||||||
|  | // 3.4 Generic replies from agent to client
 | ||||||
|  | const agentFailure = 5 | ||||||
|  | 
 | ||||||
|  | type failureAgentMsg struct{} | ||||||
|  | 
 | ||||||
|  | const agentSuccess = 6 | ||||||
|  | 
 | ||||||
|  | type successAgentMsg struct{} | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 2.5.2.
 | ||||||
|  | const agentRequestIdentities = 11 | ||||||
|  | 
 | ||||||
|  | type requestIdentitiesAgentMsg struct{} | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 2.5.2.
 | ||||||
|  | const agentIdentitiesAnswer = 12 | ||||||
|  | 
 | ||||||
|  | type identitiesAnswerAgentMsg struct { | ||||||
|  | 	NumKeys uint32 `sshtype:"12"` | ||||||
|  | 	Keys    []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 2.6.2.
 | ||||||
|  | const agentSignRequest = 13 | ||||||
|  | 
 | ||||||
|  | type signRequestAgentMsg struct { | ||||||
|  | 	KeyBlob []byte `sshtype:"13"` | ||||||
|  | 	Data    []byte | ||||||
|  | 	Flags   uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 2.6.2.
 | ||||||
|  | 
 | ||||||
|  | // 3.6 Replies from agent to client for protocol 2 key operations
 | ||||||
|  | const agentSignResponse = 14 | ||||||
|  | 
 | ||||||
|  | type signResponseAgentMsg struct { | ||||||
|  | 	SigBlob []byte `sshtype:"14"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type publicKey struct { | ||||||
|  | 	Format string | ||||||
|  | 	Rest   []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Key represents a protocol 2 public key as defined in
 | ||||||
|  | // [PROTOCOL.agent], section 2.5.2.
 | ||||||
|  | type Key struct { | ||||||
|  | 	Format  string | ||||||
|  | 	Blob    []byte | ||||||
|  | 	Comment string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func clientErr(err error) error { | ||||||
|  | 	return fmt.Errorf("agent: client error: %v", err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns the storage form of an agent key with the format, base64
 | ||||||
|  | // encoded serialized key, and the comment if it is not empty.
 | ||||||
|  | func (k *Key) String() string { | ||||||
|  | 	s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) | ||||||
|  | 
 | ||||||
|  | 	if k.Comment != "" { | ||||||
|  | 		s += " " + k.Comment | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type returns the public key type.
 | ||||||
|  | func (k *Key) Type() string { | ||||||
|  | 	return k.Format | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Marshal returns key blob to satisfy the ssh.PublicKey interface.
 | ||||||
|  | func (k *Key) Marshal() []byte { | ||||||
|  | 	return k.Blob | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Verify satisfies the ssh.PublicKey interface.
 | ||||||
|  | func (k *Key) Verify(data []byte, sig *ssh.Signature) error { | ||||||
|  | 	pubKey, err := ssh.ParsePublicKey(k.Blob) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("agent: bad public key: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return pubKey.Verify(data, sig) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type wireKey struct { | ||||||
|  | 	Format string | ||||||
|  | 	Rest   []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseKey(in []byte) (out *Key, rest []byte, err error) { | ||||||
|  | 	var record struct { | ||||||
|  | 		Blob    []byte | ||||||
|  | 		Comment string | ||||||
|  | 		Rest    []byte `ssh:"rest"` | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := ssh.Unmarshal(in, &record); err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var wk wireKey | ||||||
|  | 	if err := ssh.Unmarshal(record.Blob, &wk); err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Key{ | ||||||
|  | 		Format:  wk.Format, | ||||||
|  | 		Blob:    record.Blob, | ||||||
|  | 		Comment: record.Comment, | ||||||
|  | 	}, record.Rest, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // client is a client for an ssh-agent process.
 | ||||||
|  | type client struct { | ||||||
|  | 	// conn is typically a *net.UnixConn
 | ||||||
|  | 	conn io.ReadWriter | ||||||
|  | 	// mu is used to prevent concurrent access to the agent
 | ||||||
|  | 	mu sync.Mutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewClient returns an Agent that talks to an ssh-agent process over
 | ||||||
|  | // the given connection.
 | ||||||
|  | func NewClient(rw io.ReadWriter) Agent { | ||||||
|  | 	return &client{conn: rw} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // call sends an RPC to the agent. On success, the reply is
 | ||||||
|  | // unmarshaled into reply and replyType is set to the first byte of
 | ||||||
|  | // the reply, which contains the type of the message.
 | ||||||
|  | func (c *client) call(req []byte) (reply interface{}, err error) { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	msg := make([]byte, 4+len(req)) | ||||||
|  | 	binary.BigEndian.PutUint32(msg, uint32(len(req))) | ||||||
|  | 	copy(msg[4:], req) | ||||||
|  | 	if _, err = c.conn.Write(msg); err != nil { | ||||||
|  | 		return nil, clientErr(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var respSizeBuf [4]byte | ||||||
|  | 	if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { | ||||||
|  | 		return nil, clientErr(err) | ||||||
|  | 	} | ||||||
|  | 	respSize := binary.BigEndian.Uint32(respSizeBuf[:]) | ||||||
|  | 	if respSize > maxAgentResponseBytes { | ||||||
|  | 		return nil, clientErr(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := make([]byte, respSize) | ||||||
|  | 	if _, err = io.ReadFull(c.conn, buf); err != nil { | ||||||
|  | 		return nil, clientErr(err) | ||||||
|  | 	} | ||||||
|  | 	reply, err = unmarshal(buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, clientErr(err) | ||||||
|  | 	} | ||||||
|  | 	return reply, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) simpleCall(req []byte) error { | ||||||
|  | 	resp, err := c.call(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, ok := resp.(*successAgentMsg); ok { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return errors.New("agent: failure") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) RemoveAll() error { | ||||||
|  | 	return c.simpleCall([]byte{agentRemoveAllIdentities}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) Remove(key ssh.PublicKey) error { | ||||||
|  | 	req := ssh.Marshal(&agentRemoveIdentityMsg{ | ||||||
|  | 		KeyBlob: key.Marshal(), | ||||||
|  | 	}) | ||||||
|  | 	return c.simpleCall(req) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) Lock(passphrase []byte) error { | ||||||
|  | 	req := ssh.Marshal(&agentLockMsg{ | ||||||
|  | 		Passphrase: passphrase, | ||||||
|  | 	}) | ||||||
|  | 	return c.simpleCall(req) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) Unlock(passphrase []byte) error { | ||||||
|  | 	req := ssh.Marshal(&agentUnlockMsg{ | ||||||
|  | 		Passphrase: passphrase, | ||||||
|  | 	}) | ||||||
|  | 	return c.simpleCall(req) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // List returns the identities known to the agent.
 | ||||||
|  | func (c *client) List() ([]*Key, error) { | ||||||
|  | 	// see [PROTOCOL.agent] section 2.5.2.
 | ||||||
|  | 	req := []byte{agentRequestIdentities} | ||||||
|  | 
 | ||||||
|  | 	msg, err := c.call(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch msg := msg.(type) { | ||||||
|  | 	case *identitiesAnswerAgentMsg: | ||||||
|  | 		if msg.NumKeys > maxAgentResponseBytes/8 { | ||||||
|  | 			return nil, errors.New("agent: too many keys in agent reply") | ||||||
|  | 		} | ||||||
|  | 		keys := make([]*Key, msg.NumKeys) | ||||||
|  | 		data := msg.Keys | ||||||
|  | 		for i := uint32(0); i < msg.NumKeys; i++ { | ||||||
|  | 			var key *Key | ||||||
|  | 			var err error | ||||||
|  | 			if key, data, err = parseKey(data); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			keys[i] = key | ||||||
|  | 		} | ||||||
|  | 		return keys, nil | ||||||
|  | 	case *failureAgentMsg: | ||||||
|  | 		return nil, errors.New("agent: failed to list keys") | ||||||
|  | 	} | ||||||
|  | 	panic("unreachable") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sign has the agent sign the data using a protocol 2 key as defined
 | ||||||
|  | // in [PROTOCOL.agent] section 2.6.2.
 | ||||||
|  | func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { | ||||||
|  | 	req := ssh.Marshal(signRequestAgentMsg{ | ||||||
|  | 		KeyBlob: key.Marshal(), | ||||||
|  | 		Data:    data, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	msg, err := c.call(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch msg := msg.(type) { | ||||||
|  | 	case *signResponseAgentMsg: | ||||||
|  | 		var sig ssh.Signature | ||||||
|  | 		if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return &sig, nil | ||||||
|  | 	case *failureAgentMsg: | ||||||
|  | 		return nil, errors.New("agent: failed to sign challenge") | ||||||
|  | 	} | ||||||
|  | 	panic("unreachable") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // unmarshal parses an agent message in packet, returning the parsed
 | ||||||
|  | // form and the message type of packet.
 | ||||||
|  | func unmarshal(packet []byte) (interface{}, error) { | ||||||
|  | 	if len(packet) < 1 { | ||||||
|  | 		return nil, errors.New("agent: empty packet") | ||||||
|  | 	} | ||||||
|  | 	var msg interface{} | ||||||
|  | 	switch packet[0] { | ||||||
|  | 	case agentFailure: | ||||||
|  | 		return new(failureAgentMsg), nil | ||||||
|  | 	case agentSuccess: | ||||||
|  | 		return new(successAgentMsg), nil | ||||||
|  | 	case agentIdentitiesAnswer: | ||||||
|  | 		msg = new(identitiesAnswerAgentMsg) | ||||||
|  | 	case agentSignResponse: | ||||||
|  | 		msg = new(signResponseAgentMsg) | ||||||
|  | 	case agentV1IdentitiesAnswer: | ||||||
|  | 		msg = new(agentV1IdentityMsg) | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) | ||||||
|  | 	} | ||||||
|  | 	if err := ssh.Unmarshal(packet, msg); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return msg, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type rsaKeyMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	N           *big.Int | ||||||
|  | 	E           *big.Int | ||||||
|  | 	D           *big.Int | ||||||
|  | 	Iqmp        *big.Int // IQMP = Inverse Q Mod P
 | ||||||
|  | 	P           *big.Int | ||||||
|  | 	Q           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type dsaKeyMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	P           *big.Int | ||||||
|  | 	Q           *big.Int | ||||||
|  | 	G           *big.Int | ||||||
|  | 	Y           *big.Int | ||||||
|  | 	X           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ecdsaKeyMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	Curve       string | ||||||
|  | 	KeyBytes    []byte | ||||||
|  | 	D           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ed25519KeyMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	Pub         []byte | ||||||
|  | 	Priv        []byte | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Insert adds a private key to the agent.
 | ||||||
|  | func (c *client) insertKey(s interface{}, comment string, constraints []byte) error { | ||||||
|  | 	var req []byte | ||||||
|  | 	switch k := s.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		if len(k.Primes) != 2 { | ||||||
|  | 			return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) | ||||||
|  | 		} | ||||||
|  | 		k.Precompute() | ||||||
|  | 		req = ssh.Marshal(rsaKeyMsg{ | ||||||
|  | 			Type:        ssh.KeyAlgoRSA, | ||||||
|  | 			N:           k.N, | ||||||
|  | 			E:           big.NewInt(int64(k.E)), | ||||||
|  | 			D:           k.D, | ||||||
|  | 			Iqmp:        k.Precomputed.Qinv, | ||||||
|  | 			P:           k.Primes[0], | ||||||
|  | 			Q:           k.Primes[1], | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *dsa.PrivateKey: | ||||||
|  | 		req = ssh.Marshal(dsaKeyMsg{ | ||||||
|  | 			Type:        ssh.KeyAlgoDSA, | ||||||
|  | 			P:           k.P, | ||||||
|  | 			Q:           k.Q, | ||||||
|  | 			G:           k.G, | ||||||
|  | 			Y:           k.Y, | ||||||
|  | 			X:           k.X, | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) | ||||||
|  | 		req = ssh.Marshal(ecdsaKeyMsg{ | ||||||
|  | 			Type:        "ecdsa-sha2-" + nistID, | ||||||
|  | 			Curve:       nistID, | ||||||
|  | 			KeyBytes:    elliptic.Marshal(k.Curve, k.X, k.Y), | ||||||
|  | 			D:           k.D, | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *ed25519.PrivateKey: | ||||||
|  | 		req = ssh.Marshal(ed25519KeyMsg{ | ||||||
|  | 			Type:        ssh.KeyAlgoED25519, | ||||||
|  | 			Pub:         []byte(*k)[32:], | ||||||
|  | 			Priv:        []byte(*k), | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("agent: unsupported key type %T", s) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// if constraints are present then the message type needs to be changed.
 | ||||||
|  | 	if len(constraints) != 0 { | ||||||
|  | 		req[0] = agentAddIdConstrained | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := c.call(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, ok := resp.(*successAgentMsg); ok { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return errors.New("agent: failure") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type rsaCertMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	CertBytes   []byte | ||||||
|  | 	D           *big.Int | ||||||
|  | 	Iqmp        *big.Int // IQMP = Inverse Q Mod P
 | ||||||
|  | 	P           *big.Int | ||||||
|  | 	Q           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type dsaCertMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	CertBytes   []byte | ||||||
|  | 	X           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ecdsaCertMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	CertBytes   []byte | ||||||
|  | 	D           *big.Int | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ed25519CertMsg struct { | ||||||
|  | 	Type        string `sshtype:"17|25"` | ||||||
|  | 	CertBytes   []byte | ||||||
|  | 	Pub         []byte | ||||||
|  | 	Priv        []byte | ||||||
|  | 	Comments    string | ||||||
|  | 	Constraints []byte `ssh:"rest"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add adds a private key to the agent. If a certificate is given,
 | ||||||
|  | // that certificate is added instead as public key.
 | ||||||
|  | func (c *client) Add(key AddedKey) error { | ||||||
|  | 	var constraints []byte | ||||||
|  | 
 | ||||||
|  | 	if secs := key.LifetimeSecs; secs != 0 { | ||||||
|  | 		constraints = append(constraints, agentConstrainLifetime) | ||||||
|  | 
 | ||||||
|  | 		var secsBytes [4]byte | ||||||
|  | 		binary.BigEndian.PutUint32(secsBytes[:], secs) | ||||||
|  | 		constraints = append(constraints, secsBytes[:]...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if key.ConfirmBeforeUse { | ||||||
|  | 		constraints = append(constraints, agentConstrainConfirm) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if cert := key.Certificate; cert == nil { | ||||||
|  | 		return c.insertKey(key.PrivateKey, key.Comment, constraints) | ||||||
|  | 	} else { | ||||||
|  | 		return c.insertCert(key.PrivateKey, cert, key.Comment, constraints) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error { | ||||||
|  | 	var req []byte | ||||||
|  | 	switch k := s.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		if len(k.Primes) != 2 { | ||||||
|  | 			return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) | ||||||
|  | 		} | ||||||
|  | 		k.Precompute() | ||||||
|  | 		req = ssh.Marshal(rsaCertMsg{ | ||||||
|  | 			Type:        cert.Type(), | ||||||
|  | 			CertBytes:   cert.Marshal(), | ||||||
|  | 			D:           k.D, | ||||||
|  | 			Iqmp:        k.Precomputed.Qinv, | ||||||
|  | 			P:           k.Primes[0], | ||||||
|  | 			Q:           k.Primes[1], | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *dsa.PrivateKey: | ||||||
|  | 		req = ssh.Marshal(dsaCertMsg{ | ||||||
|  | 			Type:        cert.Type(), | ||||||
|  | 			CertBytes:   cert.Marshal(), | ||||||
|  | 			X:           k.X, | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		req = ssh.Marshal(ecdsaCertMsg{ | ||||||
|  | 			Type:        cert.Type(), | ||||||
|  | 			CertBytes:   cert.Marshal(), | ||||||
|  | 			D:           k.D, | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	case *ed25519.PrivateKey: | ||||||
|  | 		req = ssh.Marshal(ed25519CertMsg{ | ||||||
|  | 			Type:        cert.Type(), | ||||||
|  | 			CertBytes:   cert.Marshal(), | ||||||
|  | 			Pub:         []byte(*k)[32:], | ||||||
|  | 			Priv:        []byte(*k), | ||||||
|  | 			Comments:    comment, | ||||||
|  | 			Constraints: constraints, | ||||||
|  | 		}) | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("agent: unsupported key type %T", s) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// if constraints are present then the message type needs to be changed.
 | ||||||
|  | 	if len(constraints) != 0 { | ||||||
|  | 		req[0] = agentAddIdConstrained | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signer, err := ssh.NewSignerFromKey(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { | ||||||
|  | 		return errors.New("agent: signer and cert have different public key") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := c.call(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, ok := resp.(*successAgentMsg); ok { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return errors.New("agent: failure") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Signers provides a callback for client authentication.
 | ||||||
|  | func (c *client) Signers() ([]ssh.Signer, error) { | ||||||
|  | 	keys, err := c.List() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var result []ssh.Signer | ||||||
|  | 	for _, k := range keys { | ||||||
|  | 		result = append(result, &agentKeyringSigner{c, k}) | ||||||
|  | 	} | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type agentKeyringSigner struct { | ||||||
|  | 	agent *client | ||||||
|  | 	pub   ssh.PublicKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { | ||||||
|  | 	return s.pub | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { | ||||||
|  | 	// The agent has its own entropy source, so the rand argument is ignored.
 | ||||||
|  | 	return s.agent.Sign(s.pub, data) | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								vendor/golang.org/x/crypto/ssh/agent/forward.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/golang.org/x/crypto/ssh/agent/forward.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | // Copyright 2014 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 agent | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // RequestAgentForwarding sets up agent forwarding for the session.
 | ||||||
|  | // ForwardToAgent or ForwardToRemote should be called to route
 | ||||||
|  | // the authentication requests.
 | ||||||
|  | func RequestAgentForwarding(session *ssh.Session) error { | ||||||
|  | 	ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !ok { | ||||||
|  | 		return errors.New("forwarding request denied") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ForwardToAgent routes authentication requests to the given keyring.
 | ||||||
|  | func ForwardToAgent(client *ssh.Client, keyring Agent) error { | ||||||
|  | 	channels := client.HandleChannelOpen(channelType) | ||||||
|  | 	if channels == nil { | ||||||
|  | 		return errors.New("agent: already have handler for " + channelType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		for ch := range channels { | ||||||
|  | 			channel, reqs, err := ch.Accept() | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			go ssh.DiscardRequests(reqs) | ||||||
|  | 			go func() { | ||||||
|  | 				ServeAgent(keyring, channel) | ||||||
|  | 				channel.Close() | ||||||
|  | 			}() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const channelType = "auth-agent@openssh.com" | ||||||
|  | 
 | ||||||
|  | // ForwardToRemote routes authentication requests to the ssh-agent
 | ||||||
|  | // process serving on the given unix socket.
 | ||||||
|  | func ForwardToRemote(client *ssh.Client, addr string) error { | ||||||
|  | 	channels := client.HandleChannelOpen(channelType) | ||||||
|  | 	if channels == nil { | ||||||
|  | 		return errors.New("agent: already have handler for " + channelType) | ||||||
|  | 	} | ||||||
|  | 	conn, err := net.Dial("unix", addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	conn.Close() | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		for ch := range channels { | ||||||
|  | 			channel, reqs, err := ch.Accept() | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			go ssh.DiscardRequests(reqs) | ||||||
|  | 			go forwardUnixSocket(channel, addr) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func forwardUnixSocket(channel ssh.Channel, addr string) { | ||||||
|  | 	conn, err := net.Dial("unix", addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var wg sync.WaitGroup | ||||||
|  | 	wg.Add(2) | ||||||
|  | 	go func() { | ||||||
|  | 		io.Copy(conn, channel) | ||||||
|  | 		conn.(*net.UnixConn).CloseWrite() | ||||||
|  | 		wg.Done() | ||||||
|  | 	}() | ||||||
|  | 	go func() { | ||||||
|  | 		io.Copy(channel, conn) | ||||||
|  | 		channel.CloseWrite() | ||||||
|  | 		wg.Done() | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	wg.Wait() | ||||||
|  | 	conn.Close() | ||||||
|  | 	channel.Close() | ||||||
|  | } | ||||||
							
								
								
									
										215
									
								
								vendor/golang.org/x/crypto/ssh/agent/keyring.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								vendor/golang.org/x/crypto/ssh/agent/keyring.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | |||||||
|  | // Copyright 2014 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 agent | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/subtle" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type privKey struct { | ||||||
|  | 	signer  ssh.Signer | ||||||
|  | 	comment string | ||||||
|  | 	expire  *time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type keyring struct { | ||||||
|  | 	mu   sync.Mutex | ||||||
|  | 	keys []privKey | ||||||
|  | 
 | ||||||
|  | 	locked     bool | ||||||
|  | 	passphrase []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var errLocked = errors.New("agent: locked") | ||||||
|  | 
 | ||||||
|  | // NewKeyring returns an Agent that holds keys in memory.  It is safe
 | ||||||
|  | // for concurrent use by multiple goroutines.
 | ||||||
|  | func NewKeyring() Agent { | ||||||
|  | 	return &keyring{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveAll removes all identities.
 | ||||||
|  | func (r *keyring) RemoveAll() error { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return errLocked | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.keys = nil | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // removeLocked does the actual key removal. The caller must already be holding the
 | ||||||
|  | // keyring mutex.
 | ||||||
|  | func (r *keyring) removeLocked(want []byte) error { | ||||||
|  | 	found := false | ||||||
|  | 	for i := 0; i < len(r.keys); { | ||||||
|  | 		if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) { | ||||||
|  | 			found = true | ||||||
|  | 			r.keys[i] = r.keys[len(r.keys)-1] | ||||||
|  | 			r.keys = r.keys[:len(r.keys)-1] | ||||||
|  | 			continue | ||||||
|  | 		} else { | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !found { | ||||||
|  | 		return errors.New("agent: key not found") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Remove removes all identities with the given public key.
 | ||||||
|  | func (r *keyring) Remove(key ssh.PublicKey) error { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return errLocked | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return r.removeLocked(key.Marshal()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
 | ||||||
|  | func (r *keyring) Lock(passphrase []byte) error { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return errLocked | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.locked = true | ||||||
|  | 	r.passphrase = passphrase | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Unlock undoes the effect of Lock
 | ||||||
|  | func (r *keyring) Unlock(passphrase []byte) error { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if !r.locked { | ||||||
|  | 		return errors.New("agent: not locked") | ||||||
|  | 	} | ||||||
|  | 	if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) { | ||||||
|  | 		return fmt.Errorf("agent: incorrect passphrase") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.locked = false | ||||||
|  | 	r.passphrase = nil | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // expireKeysLocked removes expired keys from the keyring. If a key was added
 | ||||||
|  | // with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
 | ||||||
|  | // ellapsed, it is removed. The caller *must* be holding the keyring mutex.
 | ||||||
|  | func (r *keyring) expireKeysLocked() { | ||||||
|  | 	for _, k := range r.keys { | ||||||
|  | 		if k.expire != nil && time.Now().After(*k.expire) { | ||||||
|  | 			r.removeLocked(k.signer.PublicKey().Marshal()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // List returns the identities known to the agent.
 | ||||||
|  | func (r *keyring) List() ([]*Key, error) { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		// section 2.7: locked agents return empty.
 | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.expireKeysLocked() | ||||||
|  | 	var ids []*Key | ||||||
|  | 	for _, k := range r.keys { | ||||||
|  | 		pub := k.signer.PublicKey() | ||||||
|  | 		ids = append(ids, &Key{ | ||||||
|  | 			Format:  pub.Type(), | ||||||
|  | 			Blob:    pub.Marshal(), | ||||||
|  | 			Comment: k.comment}) | ||||||
|  | 	} | ||||||
|  | 	return ids, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Insert adds a private key to the keyring. If a certificate
 | ||||||
|  | // is given, that certificate is added as public key. Note that
 | ||||||
|  | // any constraints given are ignored.
 | ||||||
|  | func (r *keyring) Add(key AddedKey) error { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return errLocked | ||||||
|  | 	} | ||||||
|  | 	signer, err := ssh.NewSignerFromKey(key.PrivateKey) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if cert := key.Certificate; cert != nil { | ||||||
|  | 		signer, err = ssh.NewCertSigner(cert, signer) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p := privKey{ | ||||||
|  | 		signer:  signer, | ||||||
|  | 		comment: key.Comment, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if key.LifetimeSecs > 0 { | ||||||
|  | 		t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second) | ||||||
|  | 		p.expire = &t | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.keys = append(r.keys, p) | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sign returns a signature for the data.
 | ||||||
|  | func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return nil, errLocked | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.expireKeysLocked() | ||||||
|  | 	wanted := key.Marshal() | ||||||
|  | 	for _, k := range r.keys { | ||||||
|  | 		if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) { | ||||||
|  | 			return k.signer.Sign(rand.Reader, data) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, errors.New("not found") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Signers returns signers for all the known keys.
 | ||||||
|  | func (r *keyring) Signers() ([]ssh.Signer, error) { | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  | 	if r.locked { | ||||||
|  | 		return nil, errLocked | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.expireKeysLocked() | ||||||
|  | 	s := make([]ssh.Signer, 0, len(r.keys)) | ||||||
|  | 	for _, k := range r.keys { | ||||||
|  | 		s = append(s, k.signer) | ||||||
|  | 	} | ||||||
|  | 	return s, nil | ||||||
|  | } | ||||||
							
								
								
									
										451
									
								
								vendor/golang.org/x/crypto/ssh/agent/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								vendor/golang.org/x/crypto/ssh/agent/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,451 @@ | |||||||
|  | // Copyright 2012 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 agent | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/dsa" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"math/big" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ed25519" | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Server wraps an Agent and uses it to implement the agent side of
 | ||||||
|  | // the SSH-agent, wire protocol.
 | ||||||
|  | type server struct { | ||||||
|  | 	agent Agent | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *server) processRequestBytes(reqData []byte) []byte { | ||||||
|  | 	rep, err := s.processRequest(reqData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err != errLocked { | ||||||
|  | 			// TODO(hanwen): provide better logging interface?
 | ||||||
|  | 			log.Printf("agent %d: %v", reqData[0], err) | ||||||
|  | 		} | ||||||
|  | 		return []byte{agentFailure} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err == nil && rep == nil { | ||||||
|  | 		return []byte{agentSuccess} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ssh.Marshal(rep) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func marshalKey(k *Key) []byte { | ||||||
|  | 	var record struct { | ||||||
|  | 		Blob    []byte | ||||||
|  | 		Comment string | ||||||
|  | 	} | ||||||
|  | 	record.Blob = k.Marshal() | ||||||
|  | 	record.Comment = k.Comment | ||||||
|  | 
 | ||||||
|  | 	return ssh.Marshal(&record) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // See [PROTOCOL.agent], section 2.5.1.
 | ||||||
|  | const agentV1IdentitiesAnswer = 2 | ||||||
|  | 
 | ||||||
|  | type agentV1IdentityMsg struct { | ||||||
|  | 	Numkeys uint32 `sshtype:"2"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type agentRemoveIdentityMsg struct { | ||||||
|  | 	KeyBlob []byte `sshtype:"18"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type agentLockMsg struct { | ||||||
|  | 	Passphrase []byte `sshtype:"22"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type agentUnlockMsg struct { | ||||||
|  | 	Passphrase []byte `sshtype:"23"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *server) processRequest(data []byte) (interface{}, error) { | ||||||
|  | 	switch data[0] { | ||||||
|  | 	case agentRequestV1Identities: | ||||||
|  | 		return &agentV1IdentityMsg{0}, nil | ||||||
|  | 
 | ||||||
|  | 	case agentRemoveAllV1Identities: | ||||||
|  | 		return nil, nil | ||||||
|  | 
 | ||||||
|  | 	case agentRemoveIdentity: | ||||||
|  | 		var req agentRemoveIdentityMsg | ||||||
|  | 		if err := ssh.Unmarshal(data, &req); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var wk wireKey | ||||||
|  | 		if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob}) | ||||||
|  | 
 | ||||||
|  | 	case agentRemoveAllIdentities: | ||||||
|  | 		return nil, s.agent.RemoveAll() | ||||||
|  | 
 | ||||||
|  | 	case agentLock: | ||||||
|  | 		var req agentLockMsg | ||||||
|  | 		if err := ssh.Unmarshal(data, &req); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil, s.agent.Lock(req.Passphrase) | ||||||
|  | 
 | ||||||
|  | 	case agentUnlock: | ||||||
|  | 		var req agentLockMsg | ||||||
|  | 		if err := ssh.Unmarshal(data, &req); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return nil, s.agent.Unlock(req.Passphrase) | ||||||
|  | 
 | ||||||
|  | 	case agentSignRequest: | ||||||
|  | 		var req signRequestAgentMsg | ||||||
|  | 		if err := ssh.Unmarshal(data, &req); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var wk wireKey | ||||||
|  | 		if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		k := &Key{ | ||||||
|  | 			Format: wk.Format, | ||||||
|  | 			Blob:   req.KeyBlob, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		sig, err := s.agent.Sign(k, req.Data) //  TODO(hanwen): flags.
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil | ||||||
|  | 
 | ||||||
|  | 	case agentRequestIdentities: | ||||||
|  | 		keys, err := s.agent.List() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		rep := identitiesAnswerAgentMsg{ | ||||||
|  | 			NumKeys: uint32(len(keys)), | ||||||
|  | 		} | ||||||
|  | 		for _, k := range keys { | ||||||
|  | 			rep.Keys = append(rep.Keys, marshalKey(k)...) | ||||||
|  | 		} | ||||||
|  | 		return rep, nil | ||||||
|  | 
 | ||||||
|  | 	case agentAddIdConstrained, agentAddIdentity: | ||||||
|  | 		return nil, s.insertIdentity(data) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, fmt.Errorf("unknown opcode %d", data[0]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseRSAKey(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k rsaKeyMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if k.E.BitLen() > 30 { | ||||||
|  | 		return nil, errors.New("agent: RSA public exponent too large") | ||||||
|  | 	} | ||||||
|  | 	priv := &rsa.PrivateKey{ | ||||||
|  | 		PublicKey: rsa.PublicKey{ | ||||||
|  | 			E: int(k.E.Int64()), | ||||||
|  | 			N: k.N, | ||||||
|  | 		}, | ||||||
|  | 		D:      k.D, | ||||||
|  | 		Primes: []*big.Int{k.P, k.Q}, | ||||||
|  | 	} | ||||||
|  | 	priv.Precompute() | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseEd25519Key(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k ed25519KeyMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	priv := ed25519.PrivateKey(k.Priv) | ||||||
|  | 	return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseDSAKey(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k dsaKeyMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	priv := &dsa.PrivateKey{ | ||||||
|  | 		PublicKey: dsa.PublicKey{ | ||||||
|  | 			Parameters: dsa.Parameters{ | ||||||
|  | 				P: k.P, | ||||||
|  | 				Q: k.Q, | ||||||
|  | 				G: k.G, | ||||||
|  | 			}, | ||||||
|  | 			Y: k.Y, | ||||||
|  | 		}, | ||||||
|  | 		X: k.X, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) { | ||||||
|  | 	priv = &ecdsa.PrivateKey{ | ||||||
|  | 		D: privScalar, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch curveName { | ||||||
|  | 	case "nistp256": | ||||||
|  | 		priv.Curve = elliptic.P256() | ||||||
|  | 	case "nistp384": | ||||||
|  | 		priv.Curve = elliptic.P384() | ||||||
|  | 	case "nistp521": | ||||||
|  | 		priv.Curve = elliptic.P521() | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("agent: unknown curve %q", curveName) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes) | ||||||
|  | 	if priv.X == nil || priv.Y == nil { | ||||||
|  | 		return nil, errors.New("agent: point not on curve") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return priv, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseEd25519Cert(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k ed25519CertMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	pubKey, err := ssh.ParsePublicKey(k.CertBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	priv := ed25519.PrivateKey(k.Priv) | ||||||
|  | 	cert, ok := pubKey.(*ssh.Certificate) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("agent: bad ED25519 certificate") | ||||||
|  | 	} | ||||||
|  | 	return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseECDSAKey(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k ecdsaKeyMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseRSACert(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k rsaCertMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pubKey, err := ssh.ParsePublicKey(k.CertBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cert, ok := pubKey.(*ssh.Certificate) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("agent: bad RSA certificate") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
 | ||||||
|  | 	var rsaPub struct { | ||||||
|  | 		Name string | ||||||
|  | 		E    *big.Int | ||||||
|  | 		N    *big.Int | ||||||
|  | 	} | ||||||
|  | 	if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if rsaPub.E.BitLen() > 30 { | ||||||
|  | 		return nil, errors.New("agent: RSA public exponent too large") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	priv := rsa.PrivateKey{ | ||||||
|  | 		PublicKey: rsa.PublicKey{ | ||||||
|  | 			E: int(rsaPub.E.Int64()), | ||||||
|  | 			N: rsaPub.N, | ||||||
|  | 		}, | ||||||
|  | 		D:      k.D, | ||||||
|  | 		Primes: []*big.Int{k.Q, k.P}, | ||||||
|  | 	} | ||||||
|  | 	priv.Precompute() | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseDSACert(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k dsaCertMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	pubKey, err := ssh.ParsePublicKey(k.CertBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	cert, ok := pubKey.(*ssh.Certificate) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("agent: bad DSA certificate") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
 | ||||||
|  | 	var w struct { | ||||||
|  | 		Name       string | ||||||
|  | 		P, Q, G, Y *big.Int | ||||||
|  | 	} | ||||||
|  | 	if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	priv := &dsa.PrivateKey{ | ||||||
|  | 		PublicKey: dsa.PublicKey{ | ||||||
|  | 			Parameters: dsa.Parameters{ | ||||||
|  | 				P: w.P, | ||||||
|  | 				Q: w.Q, | ||||||
|  | 				G: w.G, | ||||||
|  | 			}, | ||||||
|  | 			Y: w.Y, | ||||||
|  | 		}, | ||||||
|  | 		X: k.X, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseECDSACert(req []byte) (*AddedKey, error) { | ||||||
|  | 	var k ecdsaCertMsg | ||||||
|  | 	if err := ssh.Unmarshal(req, &k); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pubKey, err := ssh.ParsePublicKey(k.CertBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	cert, ok := pubKey.(*ssh.Certificate) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("agent: bad ECDSA certificate") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
 | ||||||
|  | 	var ecdsaPub struct { | ||||||
|  | 		Name string | ||||||
|  | 		ID   string | ||||||
|  | 		Key  []byte | ||||||
|  | 	} | ||||||
|  | 	if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *server) insertIdentity(req []byte) error { | ||||||
|  | 	var record struct { | ||||||
|  | 		Type string `sshtype:"17|25"` | ||||||
|  | 		Rest []byte `ssh:"rest"` | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := ssh.Unmarshal(req, &record); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var addedKey *AddedKey | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	switch record.Type { | ||||||
|  | 	case ssh.KeyAlgoRSA: | ||||||
|  | 		addedKey, err = parseRSAKey(req) | ||||||
|  | 	case ssh.KeyAlgoDSA: | ||||||
|  | 		addedKey, err = parseDSAKey(req) | ||||||
|  | 	case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: | ||||||
|  | 		addedKey, err = parseECDSAKey(req) | ||||||
|  | 	case ssh.KeyAlgoED25519: | ||||||
|  | 		addedKey, err = parseEd25519Key(req) | ||||||
|  | 	case ssh.CertAlgoRSAv01: | ||||||
|  | 		addedKey, err = parseRSACert(req) | ||||||
|  | 	case ssh.CertAlgoDSAv01: | ||||||
|  | 		addedKey, err = parseDSACert(req) | ||||||
|  | 	case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01: | ||||||
|  | 		addedKey, err = parseECDSACert(req) | ||||||
|  | 	case ssh.CertAlgoED25519v01: | ||||||
|  | 		addedKey, err = parseEd25519Cert(req) | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("agent: not implemented: %q", record.Type) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return s.agent.Add(*addedKey) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ServeAgent serves the agent protocol on the given connection. It
 | ||||||
|  | // returns when an I/O error occurs.
 | ||||||
|  | func ServeAgent(agent Agent, c io.ReadWriter) error { | ||||||
|  | 	s := &server{agent} | ||||||
|  | 
 | ||||||
|  | 	var length [4]byte | ||||||
|  | 	for { | ||||||
|  | 		if _, err := io.ReadFull(c, length[:]); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		l := binary.BigEndian.Uint32(length[:]) | ||||||
|  | 		if l > maxAgentResponseBytes { | ||||||
|  | 			// We also cap requests.
 | ||||||
|  | 			return fmt.Errorf("agent: request too large: %d", l) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		req := make([]byte, l) | ||||||
|  | 		if _, err := io.ReadFull(c, req); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		repData := s.processRequestBytes(req) | ||||||
|  | 		if len(repData) > maxAgentResponseBytes { | ||||||
|  | 			return fmt.Errorf("agent: reply too large: %d bytes", len(repData)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		binary.BigEndian.PutUint32(length[:], uint32(len(repData))) | ||||||
|  | 		if _, err := c.Write(length[:]); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if _, err := c.Write(repData); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @ -2,6 +2,14 @@ | |||||||
| 	"comment": "", | 	"comment": "", | ||||||
| 	"ignore": "test", | 	"ignore": "test", | ||||||
| 	"package": [ | 	"package": [ | ||||||
|  | 		{ | ||||||
|  | 			"checksumSHA1": "NCdmzR+clcl2/1jUPn0vFeqjwjk=", | ||||||
|  | 			"path": "github.com/appleboy/easyssh-proxy", | ||||||
|  | 			"revision": "89c61a4555c1578454f75ae406f4e3cdded275d2", | ||||||
|  | 			"revisionTime": "2017-03-04T06:27:13Z", | ||||||
|  | 			"version": "=1.0.0", | ||||||
|  | 			"versionExact": "1.0.0" | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", | 			"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", | ||||||
| 			"path": "github.com/davecgh/go-spew/spew", | 			"path": "github.com/davecgh/go-spew/spew", | ||||||
| @ -59,8 +67,14 @@ | |||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=", | 			"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=", | ||||||
| 			"path": "golang.org/x/crypto/ssh", | 			"path": "golang.org/x/crypto/ssh", | ||||||
| 			"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", | 			"revision": "40541ccb1c6e64c947ed6f606b8a6cb4b67d7436", | ||||||
| 			"revisionTime": "2017-02-08T20:51:15Z" | 			"revisionTime": "2017-02-12T21:20:41Z" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"checksumSHA1": "SJ3Ma3Ozavxpbh1usZWBCnzMKIc=", | ||||||
|  | 			"path": "golang.org/x/crypto/ssh/agent", | ||||||
|  | 			"revision": "40541ccb1c6e64c947ed6f606b8a6cb4b67d7436", | ||||||
|  | 			"revisionTime": "2017-02-12T21:20:41Z" | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"rootPath": "github.com/appleboy/drone-ssh" | 	"rootPath": "github.com/appleboy/drone-ssh" | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Bo-Yi Wu
						Bo-Yi Wu