Skip to content

Commit

Permalink
x/crypto/ssh: public key authentication example
Browse files Browse the repository at this point in the history
Fixes golang/go#13902.

Adds public key authentication to the
password authentication example.

Change-Id: I4af0ca627fb15b617cc1ba1c6e0954b013f4d94f
Reviewed-on: https://go-review.googlesource.com/29374
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
odeke-em authored and hanwen committed Oct 4, 2016
1 parent c2f5408 commit bfc789a
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 26 deletions.
45 changes: 32 additions & 13 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,29 @@ import (
)

func ExampleNewServerConn() {
// Public key authentication is done by comparing
// the public key of a received connection
// with the entries in the authorized_keys file.
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
if err != nil {
log.Fatalf("Failed to load authorized_keys, err: %v", err)
}

authorizedKeysMap := map[string]bool{}
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
log.Fatal(err)
}

authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}

// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ssh.ServerConfig{
// Remove to disable password auth.
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
// Should use constant-time compare (or better, salt+hash) in
// a production setting.
Expand All @@ -28,6 +48,14 @@ func ExampleNewServerConn() {
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},

// Remove to disable public key auth.
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if authorizedKeysMap[string(pubKey.Marshal())] {
return nil, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}

privateBytes, err := ioutil.ReadFile("id_rsa")
Expand Down Expand Up @@ -62,6 +90,8 @@ func ExampleNewServerConn() {
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)

// Service the incoming Channel channel.

// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
Expand All @@ -74,26 +104,15 @@ func ExampleNewServerConn() {
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Fatal("could not accept channel: ", err)
log.Fatalf("Could not accept channel: %v", err)
}

// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "shell" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any
// commands, only the
// default shell.
ok = false
}
}
req.Reply(ok, nil)
req.Reply(req.Type == "shell", nil)
}
}(requests)

Expand Down
45 changes: 32 additions & 13 deletions example_test.go-e
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,29 @@ import (
)

func ExampleNewServerConn() {
// Public key authentication is done by comparing
// the public key of a received connection
// with the entries in the authorized_keys file.
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
if err != nil {
log.Fatalf("Failed to load authorized_keys, err: %v", err)
}

authorizedKeysMap := map[string]bool{}
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
log.Fatal(err)
}

authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}

// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ssh.ServerConfig{
// Remove to disable password auth.
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
// Should use constant-time compare (or better, salt+hash) in
// a production setting.
Expand All @@ -28,6 +48,14 @@ func ExampleNewServerConn() {
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},

// Remove to disable public key auth.
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if authorizedKeysMap[string(pubKey.Marshal())] {
return nil, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}

privateBytes, err := ioutil.ReadFile("id_rsa")
Expand Down Expand Up @@ -62,6 +90,8 @@ func ExampleNewServerConn() {
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)

// Service the incoming Channel channel.

// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
Expand All @@ -74,26 +104,15 @@ func ExampleNewServerConn() {
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Fatal("could not accept channel: ", err)
log.Fatalf("Could not accept channel: %v", err)
}

// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "shell" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any
// commands, only the
// default shell.
ok = false
}
}
req.Reply(ok, nil)
req.Reply(req.Type == "shell", nil)
}
}(requests)

Expand Down

0 comments on commit bfc789a

Please sign in to comment.