// connect is a private function to set up the ssh connection. It is called at the beginning of every public // function. func (config *Config) connect() (*ssh.Session, error) { sshconfig := &ssh.ClientConfig{ User: config.User, } if config.User == "" { u, err := user.Current() if err != nil { return nil, err } sshconfig.User = u.Username } if config.Password != "" { sshconfig.Auth = append(sshconfig.Auth, ssh.Password(config.Password)) } // By default, we try to include ~/.ssh/id_rsa. It is not an error if this file // doesn't exist. keyfile := os.Getenv("HOME") + "/.ssh/id_rsa" pkey, err := parsekey(keyfile) if err == nil { sshconfig.Auth = append(sshconfig.Auth, ssh.PublicKeys(pkey)) } // Include any additional key files for _, keyfile = range config.KeyFiles { pkey, err = parsekey(keyfile) if err != nil { if config.AbortOnError == true { log.Fatalf("%s", err) } return nil, err } sshconfig.Auth = append(sshconfig.Auth, ssh.PublicKeys(pkey)) } host := config.Host if strings.Contains(host, ":") == false { host = host + ":22" } client, err := ssh.Dial("tcp", host, sshconfig) if err != nil { if config.AbortOnError == true { log.Fatalf("%s", err) } return nil, err } session, err := client.NewSession() if err != nil { if config.AbortOnError == true { log.Fatalf("%s", err) } return nil, err } return session, err }
// generates an ssh config that attempt ssh-agents and then authorizes from keyFile // if keyFile is nil, we'll search for the usual suspects // (~/.ssh/id_rsa, id_dsa) func SshConf(user string, keyFile string) *ssh.ClientConfig { var auths []ssh.AuthMethod // ssh-agent auth goes first if agentPipe, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { ag := agent.NewClient(agentPipe) agentSigners, err := ag.Signers() if err != nil { log.Printf("Error pulling signers from ssh-agent: %s", err) } else { if len(agentSigners) > 0 { auths = append(auths, ssh.PublicKeys(agentSigners...)) } } } // provided keyfile or default keyfiles var keyFiles []string if keyFile != "" { keyFiles = []string{keyFile} } else { keyFiles = lookupKeyFiles() } signers := make([]ssh.Signer, 0, 0) for _, keyFile := range keyFiles { keyFileH, err := os.Open(keyFile) if err != nil { log.Printf("Error opening keyFile %s : %s", keyFile, err) continue } keyBytes, err := ioutil.ReadAll(keyFileH) keyFileH.Close() if err != nil { log.Printf("Error reading keyFile %s, skipping : %s", keyFile, err) continue } signer, err := ssh.ParsePrivateKey(keyBytes) if err != nil { log.Printf("Error parsing keyFile contents from %s, skipping: %s", keyFile, err) } signers = append(signers, signer) } auths = append(auths, ssh.PublicKeys(signers...)) return &ssh.ClientConfig{ User: user, Auth: auths, } }
func (self *Server) ParsePrivateKey(filePath string) error { if filePath == "" { return nil } keyBytes, err := ioutil.ReadFile(filePath) if err != nil { err = errors.New(fmt.Sprintf("Could not read ssh key \"%s\" : %s ", filePath, err.Error())) self.Error = err self.ErrorMsg = err.Error() return err } signer, err := ssh.ParsePrivateKey(keyBytes) if err != nil { err = errors.New(fmt.Sprintf("Could not parse ssh key \"%s\" : %s ", filePath, err.Error())) self.Error = err self.ErrorMsg = err.Error() return err } self.PrivateKeyPath = filePath self.AuthMethods = append(self.AuthMethods, ssh.PublicKeys(signer)) return nil }
// remoteCmdOutput runs the given command on a remote server at the given hostname as the given user. func remoteCmdOutput(username, hostname, cmd string, privateKey []byte) (b []byte, err error) { p, err := ssh.ParseRawPrivateKey(privateKey) if err != nil { return b, err } s, err := ssh.NewSignerFromKey(p) if err != nil { return b, err } pub := ssh.PublicKeys(s) clientConfig := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{pub}, } client, err := ssh.Dial("tcp", hostname, clientConfig) if err != nil { return b, errors.New("ERROR: Failed to dial: " + err.Error()) } defer client.Close() session, err := client.NewSession() if err != nil { return b, errors.New("ERROR: Failed to create session: " + err.Error()) } defer session.Close() b, err = session.Output(cmd) if err != nil { return b, fmt.Errorf("ERROR: Failed to run cmd on host %s: %s", hostname, err.Error()) } return b, nil }
func main() { key, err := getKeyFile() if err != nil { panic(err) } config := &ssh.ClientConfig{ User: "******", Auth: []ssh.AuthMethod{ssh.PublicKeys(key)}, } client, err := ssh.Dial("tcp", "localhost:22", config) if err != nil { panic(err) } session, err := client.NewSession() if err != nil { panic("Failed to create session: " + err.Error()) } defer session.Close() var b bytes.Buffer session.Stdout = &b if err := session.Run("/usr/bin/whoami"); err != nil { panic(err.Error()) } fmt.Println(b.String()) } //godoc -http=:1989 -index=true
func TestCertLogin(t *testing.T) { s := newServer(t) defer s.Shutdown() // Use a key different from the default. clientKey := testSigners["dsa"] caAuthKey := testSigners["ecdsa"] cert := &ssh.Certificate{ Key: clientKey.PublicKey(), ValidPrincipals: []string{username()}, CertType: ssh.UserCert, ValidBefore: ssh.CertTimeInfinity, } if err := cert.SignCert(rand.Reader, caAuthKey); err != nil { t.Fatalf("SetSignature: %v", err) } certSigner, err := ssh.NewCertSigner(cert, clientKey) if err != nil { t.Fatalf("NewCertSigner: %v", err) } conf := &ssh.ClientConfig{ User: username(), } conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner)) client, err := s.TryDial(conf) if err != nil { t.Fatalf("TryDial: %v", err) } client.Close() }
//TODO 某种认证方法只有一个会被使用,需要多次猜测 func DialInConsole(addr string, username string) (client *ssh.Client, err error) { //find cert file pathList := certFilePathList() authList := []ssh.AuthMethod{} for _, path := range pathList { clientKeyBytes, err := ioutil.ReadFile(path) if err != nil { if !os.IsNotExist(err) { return nil, fmt.Errorf("[DialInConsole] ioutil.ReadFile() err:%s", err) } } else { signer, err := ssh.ParsePrivateKey(clientKeyBytes) if err != nil { return nil, fmt.Errorf("[DialInConsole] ssh.ParsePrivateKey err:%s", err) } //clientKey := &keychain{signer} authList = append(authList, ssh.PublicKeys(signer)) } } authList = append(authList, ssh.PasswordCallback(func() (secret string, err error) { fmt.Printf("[ssh] password for %s@%s", username, addr) secret = string(gopass.GetPasswd()) return })) clientConfig := &ssh.ClientConfig{ User: username, Auth: authList, } client, err = ssh.Dial("tcp", addr, clientConfig) if err != nil { return nil, fmt.Errorf("[DialInConsole] Failed to dial: %s", err.Error()) } return }
func main() { cmd := "hostname" username := os.Args[1] hosts := os.Args[2:] results := make(chan string, 10) timeout := time.After(10 * time.Second) key, err := getKeyFile() panicIf(err) config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(key), }, } for _, hostname := range hosts { go func(hostname string) { results <- executeCmd(cmd, hostname, config) }(hostname) } for _, _ = range hosts { select { case res := <-results: fmt.Println(res) case <-timeout: fmt.Println("Timed out!") return } } }
// PrepareConfig is used to turn the *SSHConfig provided into a // usable *Config for client initialization. func PrepareConfig(conf *SSHConfig) (*Config, error) { sshConf := &ssh.ClientConfig{ User: conf.User, } if conf.KeyFile != "" { key, err := ioutil.ReadFile(conf.KeyFile) if err != nil { return nil, fmt.Errorf("Failed to read key file '%s': %v", conf.KeyFile, err) } signer, err := ssh.ParsePrivateKey(key) if err != nil { return nil, fmt.Errorf("Failed to parse key file '%s': %v", conf.KeyFile, err) } sshConf.Auth = append(sshConf.Auth, ssh.PublicKeys(signer)) } if conf.Password != "" { sshConf.Auth = append(sshConf.Auth, ssh.Password(conf.Password)) sshConf.Auth = append(sshConf.Auth, ssh.KeyboardInteractive(PasswordKeyboardInteractive(conf.Password))) } host := fmt.Sprintf("%s:%d", conf.Host, conf.Port) config := &Config{ SSHConfig: sshConf, Connection: ConnectFunc("tcp", host), } return config, nil }
// Dials to setup tcp connection to remote host, used for memory information func dialClient() { t_key, _ := getKeyFile() if err != nil { panic(err) } key = t_key config := &ssh.ClientConfig{ User: remote_host_user, Auth: []ssh.AuthMethod{ ssh.PublicKeys(key), }, } t_client, err := ssh.Dial("tcp", remote_host+":"+ssh_port, config) if err != nil { fmt.Println("\n", "Failed to dial: "+err.Error()) fmt.Println("Unable to establish connection to remote machine.") fmt.Println("Make sure that password-less connection is possible.") fmt.Println("************************************") mem_flag = false return } ssh_client = t_client }
func (d *sshDialer) Dial(addr string) (connMuxer, error) { conf := ssh.ClientConfig{ User: "******", Auth: []ssh.AuthMethod{ssh.PublicKeys(d.identity)}, HostKeyCallback: d.checkHost, } c, err := net.Dial("tcp", addr) if err != nil { return nil, err } defer func() { if c != nil { c.Close() } }() conn, chans, reqs, err := ssh.NewClientConn(c, addr, &conf) if err != nil { return nil, err } go ssh.DiscardRequests(reqs) go func() { for c := range chans { go c.Reject(ssh.Prohibited, "") } }() c = nil return &sshMuxer{conn}, nil }
func getSftpClient(conf Config) []*sftp.Client { // process the keyfile buf, err := ioutil.ReadFile(conf.KeyFile) if err != nil { log.Fatalf("error in reading private key file %s\n", err) } key, err := ssh.ParsePrivateKey(buf) if err != nil { log.Fatalf("error in parsing private key %s\n", key) } // client config config := &ssh.ClientConfig{ User: conf.User, Auth: []ssh.AuthMethod{ssh.PublicKeys(key)}, } // connection clients := make([]*sftp.Client, 0) for _, r := range conf.Remotes { c, err := ssh.Dial("tcp", r, config) if err != nil { log.Fatalf("error in ssh connection %s\n", err) } // sftp handler sftp, err := sftp.NewClient(c) if err != nil { log.Fatalf("error in sftp connection %s\n", err) } clients = append(clients, sftp) } return clients }
func (c *SftpClient) Connect() error { auth := []ssh.AuthMethod{} if c.authMethod == "key" { key, _ := c.GetKey(c.keyPath) auth = []ssh.AuthMethod{ ssh.PublicKeys(key), } } else if c.authMethod == "password" { auth = []ssh.AuthMethod{ ssh.Password(c.password), } } config := &ssh.ClientConfig{ User: c.username, Auth: auth, } sHost := strings.Join([]string{c.hostname, strconv.FormatInt(c.port, 10)}, ":") sshClient, err := ssh.Dial("tcp", sHost, config) if err != nil { return err } sftpClient, err := sftp.NewClient(sshClient) if err == nil { c.Client = sftpClient } return err }
func NewClient(user, address string, privateKey []byte) (*client, error) { c := &client{} config := &ssh.ClientConfig{ User: user, } // Check if we've been given a byte slice from which to parse a key. if privateKey != nil { privateKeyParsed, err := ssh.ParsePrivateKey(privateKey) if err != nil { return c, err } config.Auth = []ssh.AuthMethod{ ssh.PublicKeys(privateKeyParsed), } } else { sshAuthSock := os.Getenv(`SSH_AUTH_SOCK`) socket, err := net.Dial("unix", sshAuthSock) if err != nil { return c, err } sshAgent := agent.NewClient(socket) signers, err := sshAgent.Signers() if err != nil { return c, err } config.Auth = []ssh.AuthMethod{ ssh.PublicKeys(signers...), } } client, err := ssh.Dial("tcp", address, config) if err != nil { return c, err } c.client = client return c, nil }
func MakeKeyring() ssh.AuthMethod { signer, err := makeSigner(os.Getenv("HOME") + "/.ssh/alex.sharov") if err != nil { panic(err) } return ssh.PublicKeys(signer) }
func clientConfig() *ssh.ClientConfig { config := &ssh.ClientConfig{ User: username(), Auth: []ssh.AuthMethod{ ssh.PublicKeys(testSigners["user"]), }, HostKeyCallback: hostKeyDB().Check, } return config }
func (self *Script) Execute(host *Host, out io.Writer) error { usr, err := user.Current() if err != nil { return err } if host.User == "" { host.User = usr.Username } cfg := &ssh.ClientConfig{ User: host.User, } if host.Password != "" { cfg.Auth = []ssh.AuthMethod{ ssh.Password(host.Password), } } else { content, err := ioutil.ReadFile(usr.HomeDir + "/.ssh/id_rsa") if err != nil { content, err = ioutil.ReadFile(usr.HomeDir + "/.ssh/id_dsa") if err != nil { return err } } key, err := ssh.ParsePrivateKey(content) if err != nil { return err } cfg.Auth = []ssh.AuthMethod{ssh.PublicKeys(key)} } client, err := ssh.Dial("tcp", host.Name+":"+strconv.Itoa(host.Port), cfg) if err != nil { fmt.Fprintln(out, err.Error()) return err } session, err := client.NewSession() if err != nil { fmt.Fprintln(out, err.Error()) return err } defer session.Close() session.Stdout = out session.Stderr = out if !self.HideBoundaries { fmt.Fprintln(out, "---------------------- script started ----------------------") } if err := session.Run(self.Content); err != nil { return err } if !self.HideBoundaries { fmt.Fprintln(out, "---------------------- script finished ----------------------") } return nil }
// SSH connects to the specified workers and runs the specified command. If the // command does not complete in the given duration then all remaining workers are // considered timed out. SSH also automatically substitutes the sequential number // of the worker for the WORKER_NUM_KEYWORD since it is a common use case. func SSH(cmd string, workers []string, timeout time.Duration) (map[string]string, error) { glog.Infof("Running \"%s\" on %s with timeout of %s", cmd, workers, timeout) // Ensure that the key file exists. key, err := getKeyFile() if err != nil { return nil, fmt.Errorf("Failed to get key file: %s", err) } // Initialize the structure with the configuration for ssh. config := &ssh.ClientConfig{ User: CT_USER, Auth: []ssh.AuthMethod{ ssh.PublicKeys(key), }, } var wg sync.WaitGroup // Will be populated and returned by this function. workersWithOutputs := map[string]string{} // Keeps track of which workers are still pending. remainingWorkers := map[string]int{} // Kick off a goroutine on all workers. for i, hostname := range workers { wg.Add(1) remainingWorkers[hostname] = 1 go func(index int, hostname string) { defer wg.Done() updatedCmd := strings.Replace(cmd, WORKER_NUM_KEYWORD, strconv.Itoa(index+1), -1) output, err := executeCmd(updatedCmd, hostname, config, timeout) if err != nil { glog.Errorf("Could not execute ssh cmd: %s", err) } workersWithOutputs[hostname] = output delete(remainingWorkers, hostname) glog.Infoln() glog.Infof("[%d/%d] Worker %s has completed execution", NUM_WORKERS-len(remainingWorkers), NUM_WORKERS, hostname) glog.Infof("Remaining workers: %v", remainingWorkers) }(i, hostname) } wg.Wait() glog.Infoln() glog.Infof("Finished running \"%s\" on all %d workers", cmd, NUM_WORKERS) glog.Info("========================================") return workersWithOutputs, nil }
func GetSshClient(username string, privateKey []byte, ip string) (*awsSshClient, error) { signer, err := ssh.ParsePrivateKey(privateKey) if err != nil { return nil, err } config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, } client, err := ssh.Dial("tcp", ip+":22", config) return &awsSshClient{client: client}, err }
func getPublicKey() ssh.AuthMethod { fh, err := os.Open(os.ExpandEnv("$HOME/.ssh/id_rsa")) if err != nil { log.Fatalf("Could not open id_rsa: %v\n", err) } defer fh.Close() b, err := ioutil.ReadAll(fh) if err != nil { log.Fatalf("Could not read id_rsa: %v\n", err) } signer, err := ssh.ParsePrivateKey(b) if err != nil { log.Fatalf("Could not parse id_rsa: %v\n", err) } return ssh.PublicKeys(signer) }
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { config := state.Get("config").(config) privateKey := state.Get("ssh_private_key").(string) signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { return nil, fmt.Errorf("Error setting up SSH config: %s", err) } return &ssh.ClientConfig{ User: config.SshUserName, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, }, nil }
// PrepareConfig is used to turn the *SSHConfig provided into a // usable *Config for client initialization. func PrepareConfig(conf *SSHConfig) (*Config, error) { sshConf := &ssh.ClientConfig{ User: conf.User, } if conf.KeyFile != "" { fullPath, err := homedir.Expand(conf.KeyFile) if err != nil { return nil, fmt.Errorf("Failed to expand home directory: %v", err) } key, err := ioutil.ReadFile(fullPath) if err != nil { return nil, fmt.Errorf("Failed to read key file '%s': %v", conf.KeyFile, err) } // We parse the private key on our own first so that we can // show a nicer error if the private key has a password. block, _ := pem.Decode(key) if block == nil { return nil, fmt.Errorf( "Failed to read key '%s': no key found", conf.KeyFile) } if block.Headers["Proc-Type"] == "4,ENCRYPTED" { return nil, fmt.Errorf( "Failed to read key '%s': password protected keys are\n"+ "not supported. Please decrypt the key prior to use.", conf.KeyFile) } signer, err := ssh.ParsePrivateKey(key) if err != nil { return nil, fmt.Errorf("Failed to parse key file '%s': %v", conf.KeyFile, err) } sshConf.Auth = append(sshConf.Auth, ssh.PublicKeys(signer)) } if conf.Password != "" { sshConf.Auth = append(sshConf.Auth, ssh.Password(conf.Password)) sshConf.Auth = append(sshConf.Auth, ssh.KeyboardInteractive(PasswordKeyboardInteractive(conf.Password))) } host := fmt.Sprintf("%s:%d", conf.Host, conf.Port) config := &Config{ SSHConfig: sshConf, Connection: ConnectFunc("tcp", host), } return config, nil }
func (c *container) dialSSH() (*ssh.Client, error) { key, err := ssh.ParseRawPrivateKey([]byte(c.PrivateKey)) if err != nil { return nil, err } signer, err := ssh.NewSignerFromKey(key) if err != nil { return nil, err } host := c.HostAddr + ":" + c.SSHHostPort config := ssh.ClientConfig{ Config: ssh.Config{Rand: rand.Reader}, Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, User: c.User, } return ssh.Dial("tcp", host, &config) }
func portMapping(job *engine.Job, remoteHost string, localPort int, remotePort int) { localListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", localPort)) if err != nil { job.Errorf("\nnet.Listen failed: %v", err) } config := &ssh.ClientConfig{ User: "******", Auth: []ssh.AuthMethod{ ssh.PublicKeys(getPrivateKeys(job)), }, } // Dial your ssh server. conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:22", remoteHost), config) if err != nil { job.Errorf("\nUnable to connect: %s with %s", err, remoteHost) } else { job.Logf("\nEstablish ssh tunnel with %s:22 %d:%d", remoteHost, localPort, remotePort) } defer conn.Close() for { // Setup localConn (type net.Conn) localConnection, err := localListener.Accept() if err != nil { job.Errorf("\nListen.Accept failed: %v", err) } defer localConnection.Close() remoteConnection, err := conn.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", remotePort)) if err != nil { job.Errorf("\nUnable to register tcp forward: %v", err) } defer remoteConnection.Close() go ioProxy(localConnection, remoteConnection) } defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() }
// SSHConfig returns a function that can be used for the SSH communicator // config for connecting to the instance created over SSH using the generated // private key. func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) { return func(state multistep.StateBag) (*ssh.ClientConfig, error) { privateKey := state.Get("privateKey").(string) signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { return nil, fmt.Errorf("Error setting up SSH config: %s", err) } return &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, }, nil } }
//DialWithCertFile //addr like 127.0.0.1:22 //username is the username on that remote machine //clientKey is the content of ~/.ssh/id_rsa func DialWithCertFile(addr string, username string, clientKeyBytes []byte) (client *ssh.Client, err error) { signer, err := ssh.ParsePrivateKey(clientKeyBytes) if err != nil { return } clientConfig := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, } client, err = ssh.Dial("tcp", addr, clientConfig) if err != nil { return nil, fmt.Errorf("[DialWithCertFile] Failed to dial: %s", err.Error()) } return }
func buildConfig() *ssh.ClientConfig { // config for ssh connection pemBytes, err := ioutil.ReadFile(*keyfile) if err != nil { log.Fatal(err) } signer, err := ssh.ParsePrivateKey(pemBytes) if err != nil { log.Fatal(err) } return &ssh.ClientConfig{ User: *user, Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, } }
func (s *Command) getSSHAuthMethods() ([]ssh.AuthMethod, error) { var methods []ssh.AuthMethod if s.Password != nil { methods = append(methods, ssh.Password(*s.Password)) } if s.IdentityFile != nil { key, err := s.getSSHKey(*s.IdentityFile) if err != nil { return nil, err } methods = append(methods, ssh.PublicKeys(key)) } return methods, nil }
func copyToRemote(c *cli.Context) { for _, flag := range []string{"keyfile", "to", "host", "user"} { if c.Generic(flag) == nil { log.Fatalf("flag %s is required\n", flag) } } // process the keyfile buf, err := ioutil.ReadFile(c.String("keyfile")) if err != nil { log.Fatalf("error in reading private key file %s\n", err) } key, err := ssh.ParsePrivateKey(buf) if err != nil { log.Fatalf("error in parsing private key %s\n", key) } // client config config := &ssh.ClientConfig{ User: c.String("user"), Auth: []ssh.AuthMethod{ssh.PublicKeys(key)}, } // connection client, err := ssh.Dial("tcp", c.String("host")+":22", config) if err != nil { log.Fatalf("error in ssh connection %s\n", err) } defer client.Close() // sftp handler sftp, err := sftp.NewClient(client) if err != nil { log.Fatalf("error in sftp connection %s\n", err) } defer sftp.Close() // Remote file r, err := sftp.Create(filepath.Join(c.String("to"), "go_sftp.txt")) if err != nil { log.Fatalf("error in creating remote file %s\n", err) } l := strings.NewReader("Writing through golang sftp") if _, err := io.Copy(r, l); err != nil { log.Fatalf("error in writing file to remote system %s\n", err) } log.Println("written new file to remote system") }
// sshConfig returns the ssh configuration. func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { log.Print("Getting SSH config...") config := state.Get("config").(*Config) log.Printf("sshConfig.User is '%s'\n", config.SSHUsername) privateKey := string(config.privateKeyBytes) signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { return nil, fmt.Errorf("Error setting up SSH config: %s", err) } return &ssh.ClientConfig{ User: config.SSHUsername, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, }, nil }