[ssh] Simple SSH and SFTP support
This can be used to connect to Fuchsia devices that support SSH.
Change-Id: I0fadcbaf0670f1f23f332f07becdfb64e56a3b9a
diff --git a/go.mod b/go.mod
index 0d3c6ee..13dfa3a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,8 @@
require (
github.com/google/subcommands v0.0.0-20181012225330-46f0354f6315
github.com/google/uuid v1.1.0
- go.chromium.org/luci v0.0.0-20181205024016-0c89bd1bcf4f
+ go.chromium.org/luci v0.0.0-20181218015242-20acb618582d
+ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181029044818-c44066c5c816
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35
)
diff --git a/go.sum b/go.sum
index 664dcc7..3f5cd1e 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,10 @@
github.com/google/subcommands v0.0.0-20181012225330-46f0354f6315/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-go.chromium.org/luci v0.0.0-20181205024016-0c89bd1bcf4f h1:YHpVpP44+EX/FdtkLuityBjE7JHl6Yhm9SVVToihk+s=
-go.chromium.org/luci v0.0.0-20181205024016-0c89bd1bcf4f/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM=
+go.chromium.org/luci v0.0.0-20181218015242-20acb618582d h1:WWlp6PQtC8FyaxytRO5UBYFBDcPOYy6+o7JmcvgLMuU=
+go.chromium.org/luci v0.0.0-20181218015242-20acb618582d/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
diff --git a/ssh/ssh.go b/ssh/ssh.go
new file mode 100644
index 0000000..b47d978
--- /dev/null
+++ b/ssh/ssh.go
@@ -0,0 +1,79 @@
+// Copyright 2018 The Fuchsia 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 ssh
+
+import (
+ "context"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "time"
+
+ "fuchsia.googlesource.com/tools/retry"
+
+ "golang.org/x/crypto/ssh"
+)
+
+const (
+ // Default SSH server port.
+ Port = 22
+
+ // Default RSA key size.
+ KeySize = 2048
+)
+
+// GenerateKeyPair generates a pair of private/public keys.
+func GenerateKeyPairt(bitSize int) ([]byte, []byte, error) {
+ key, err := rsa.GenerateKey(rand.Reader, bitSize)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pubkey, err := ssh.NewPublicKey(&key.PublicKey)
+ if err != nil {
+ return nil, nil, err
+ }
+ pembuf := pubkey.Marshal()
+
+ var privateKey = &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: x509.MarshalPKCS1PrivateKey(key),
+ }
+ buf := pem.EncodeToMemory(privateKey)
+
+ return pembuf, buf, nil
+}
+
+// Connect establishes a new SSH connection to a server with the given
+// address and port, using the provided user name and private key.
+func Connect(ctx context.Context, address string, port int, user string, privateKey []byte) (*ssh.Client, error) {
+ signer, err := ssh.ParsePrivateKey(privateKey)
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse the private key: %v", err)
+ }
+
+ config := &ssh.ClientConfig{
+ User: user,
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(signer),
+ },
+ Timeout: time.Minute, // TODO: allow passing the timeout
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+
+ var client *ssh.Client
+ addr := fmt.Sprintf("%s:%d", address, port)
+ // TODO: figure out optimal backoff time and number of retries
+ err = retry.Retry(ctx, retry.WithMaxRetries(retry.NewConstantBackoff(time.Second), 10), func() error {
+ client, err = ssh.Dial("tcp", addr, config)
+ return err
+ })
+ if err != nil {
+ return nil, fmt.Errorf("cannot connect to \"%s\": %v", addr, err)
+ }
+
+ return client, nil
+}