mirror of
https://github.com/golang/go
synced 2024-11-20 03:14:43 -07:00
exp/ssh: add client side support for publickey auth
client.go/client_auth.go: * add support for publickey key auth using the interface outlined by rsc in the previous auth CL client_auth_test.go: * password and publickey tests against server.go common.go/server.go: * move some helper methods from server.go into common.go * generalise serializeRSASignature R=rsc, agl, huin CC=cw, golang-dev, n13m3y3r https://golang.org/cl/5373055
This commit is contained in:
parent
90ec203318
commit
3ee171d174
@ -35,10 +35,6 @@ func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := conn.authenticate(); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go conn.mainLoop()
|
go conn.mainLoop()
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
@ -128,7 +124,10 @@ func (c *ClientConn) handshake() error {
|
|||||||
if packet[0] != msgNewKeys {
|
if packet[0] != msgNewKeys {
|
||||||
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
||||||
}
|
}
|
||||||
return c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc)
|
if err := c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.authenticate(H)
|
||||||
}
|
}
|
||||||
|
|
||||||
// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
|
// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authenticate authenticates with the remote server. See RFC 4252.
|
// authenticate authenticates with the remote server. See RFC 4252.
|
||||||
func (c *ClientConn) authenticate() error {
|
func (c *ClientConn) authenticate(session []byte) error {
|
||||||
// initiate user auth session
|
// initiate user auth session
|
||||||
if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
|
if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -26,7 +28,7 @@ func (c *ClientConn) authenticate() error {
|
|||||||
// then any untried methods suggested by the server.
|
// then any untried methods suggested by the server.
|
||||||
tried, remain := make(map[string]bool), make(map[string]bool)
|
tried, remain := make(map[string]bool), make(map[string]bool)
|
||||||
for auth := ClientAuth(new(noneAuth)); auth != nil; {
|
for auth := ClientAuth(new(noneAuth)); auth != nil; {
|
||||||
ok, methods, err := auth.auth(c.config.User, c.transport)
|
ok, methods, err := auth.auth(session, c.config.User, c.transport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -60,7 +62,7 @@ type ClientAuth interface {
|
|||||||
// Returns true if authentication is successful.
|
// Returns true if authentication is successful.
|
||||||
// If authentication is not successful, a []string of alternative
|
// If authentication is not successful, a []string of alternative
|
||||||
// method names is returned.
|
// method names is returned.
|
||||||
auth(user string, t *transport) (bool, []string, error)
|
auth(session []byte, user string, t *transport) (bool, []string, error)
|
||||||
|
|
||||||
// method returns the RFC 4252 method name.
|
// method returns the RFC 4252 method name.
|
||||||
method() string
|
method() string
|
||||||
@ -69,7 +71,7 @@ type ClientAuth interface {
|
|||||||
// "none" authentication, RFC 4252 section 5.2.
|
// "none" authentication, RFC 4252 section 5.2.
|
||||||
type noneAuth int
|
type noneAuth int
|
||||||
|
|
||||||
func (n *noneAuth) auth(user string, t *transport) (bool, []string, error) {
|
func (n *noneAuth) auth(session []byte, user string, t *transport) (bool, []string, error) {
|
||||||
if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
|
if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
|
||||||
User: user,
|
User: user,
|
||||||
Service: serviceSSH,
|
Service: serviceSSH,
|
||||||
@ -102,7 +104,7 @@ type passwordAuth struct {
|
|||||||
ClientPassword
|
ClientPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *passwordAuth) auth(user string, t *transport) (bool, []string, error) {
|
func (p *passwordAuth) auth(session []byte, user string, t *transport) (bool, []string, error) {
|
||||||
type passwordAuthMsg struct {
|
type passwordAuthMsg struct {
|
||||||
User string
|
User string
|
||||||
Service string
|
Service string
|
||||||
@ -155,3 +157,141 @@ type ClientPassword interface {
|
|||||||
func ClientAuthPassword(impl ClientPassword) ClientAuth {
|
func ClientAuthPassword(impl ClientPassword) ClientAuth {
|
||||||
return &passwordAuth{impl}
|
return &passwordAuth{impl}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientKeyring implements access to a client key ring.
|
||||||
|
type ClientKeyring interface {
|
||||||
|
// Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
|
||||||
|
// no key exists at i.
|
||||||
|
Key(i int) (key interface{}, err error)
|
||||||
|
|
||||||
|
// Sign returns a signature of the given data using the i'th key
|
||||||
|
// and the supplied random source.
|
||||||
|
Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "publickey" authentication, RFC 4252 Section 7.
|
||||||
|
type publickeyAuth struct {
|
||||||
|
ClientKeyring
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *publickeyAuth) auth(session []byte, user string, t *transport) (bool, []string, error) {
|
||||||
|
type publickeyAuthMsg struct {
|
||||||
|
User string
|
||||||
|
Service string
|
||||||
|
Method string
|
||||||
|
// HasSig indicates to the reciver packet that the auth request is signed and
|
||||||
|
// should be used for authentication of the request.
|
||||||
|
HasSig bool
|
||||||
|
Algoname string
|
||||||
|
Pubkey string
|
||||||
|
// Sig is defined as []byte so marshal will exclude it during the query phase
|
||||||
|
Sig []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication is performed in two stages. The first stage sends an
|
||||||
|
// enquiry to test if each key is acceptable to the remote. The second
|
||||||
|
// stage attempts to authenticate with the valid keys obtained in the
|
||||||
|
// first stage.
|
||||||
|
|
||||||
|
var index int
|
||||||
|
// a map of public keys to their index in the keyring
|
||||||
|
validKeys := make(map[int]interface{})
|
||||||
|
for {
|
||||||
|
key, err := p.Key(index)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
// no more keys in the keyring
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pubkey := serializePublickey(key)
|
||||||
|
algoname := algoName(key)
|
||||||
|
msg := publickeyAuthMsg{
|
||||||
|
User: user,
|
||||||
|
Service: serviceSSH,
|
||||||
|
Method: p.method(),
|
||||||
|
HasSig: false,
|
||||||
|
Algoname: algoname,
|
||||||
|
Pubkey: string(pubkey),
|
||||||
|
}
|
||||||
|
if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
packet, err := t.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
switch packet[0] {
|
||||||
|
case msgUserAuthPubKeyOk:
|
||||||
|
msg := decode(packet).(*userAuthPubKeyOkMsg)
|
||||||
|
if msg.Algo != algoname || msg.PubKey != string(pubkey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validKeys[index] = key
|
||||||
|
case msgUserAuthFailure:
|
||||||
|
default:
|
||||||
|
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods that may continue if this auth is not successful.
|
||||||
|
var methods []string
|
||||||
|
for i, key := range validKeys {
|
||||||
|
pubkey := serializePublickey(key)
|
||||||
|
algoname := algoName(key)
|
||||||
|
// TODO(dfc) use random source from the ClientConfig
|
||||||
|
sign, err := p.Sign(i, rand.Reader, buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||||
|
User: user,
|
||||||
|
Service: serviceSSH,
|
||||||
|
Method: p.method(),
|
||||||
|
}, []byte(algoname), pubkey))
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
// manually wrap the serialized signature in a string
|
||||||
|
s := serializeSignature(algoname, sign)
|
||||||
|
sig := make([]byte, stringLength(s))
|
||||||
|
marshalString(sig, s)
|
||||||
|
msg := publickeyAuthMsg{
|
||||||
|
User: user,
|
||||||
|
Service: serviceSSH,
|
||||||
|
Method: p.method(),
|
||||||
|
HasSig: true,
|
||||||
|
Algoname: algoname,
|
||||||
|
Pubkey: string(pubkey),
|
||||||
|
Sig: sig,
|
||||||
|
}
|
||||||
|
p := marshal(msgUserAuthRequest, msg)
|
||||||
|
if err := t.writePacket(p); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
packet, err := t.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
switch packet[0] {
|
||||||
|
case msgUserAuthSuccess:
|
||||||
|
return true, nil, nil
|
||||||
|
case msgUserAuthFailure:
|
||||||
|
msg := decode(packet).(*userAuthFailureMsg)
|
||||||
|
methods = msg.Methods
|
||||||
|
continue
|
||||||
|
case msgDisconnect:
|
||||||
|
return false, nil, io.EOF
|
||||||
|
default:
|
||||||
|
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, methods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *publickeyAuth) method() string {
|
||||||
|
return "publickey"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientAuthPublickey returns a ClientAuth using public key authentication.
|
||||||
|
func ClientAuthPublickey(impl ClientKeyring) ClientAuth {
|
||||||
|
return &publickeyAuth{impl}
|
||||||
|
}
|
||||||
|
248
src/pkg/exp/ssh/client_auth_test.go
Normal file
248
src/pkg/exp/ssh/client_auth_test.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
// Copyright 2011 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 ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _pem = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
|
||||||
|
70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
|
||||||
|
9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
|
||||||
|
tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
|
||||||
|
s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
|
||||||
|
qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
|
||||||
|
+IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
|
||||||
|
riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
|
||||||
|
D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
|
||||||
|
atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
|
||||||
|
b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
|
||||||
|
ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
|
||||||
|
MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
|
||||||
|
KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
|
||||||
|
e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
|
||||||
|
D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
|
||||||
|
3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
|
||||||
|
orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
|
||||||
|
64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
|
||||||
|
XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
|
||||||
|
QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
|
||||||
|
/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
|
||||||
|
I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
|
||||||
|
gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
|
||||||
|
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
|
||||||
|
// reused internally by tests
|
||||||
|
var serverConfig = new(ServerConfig)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := serverConfig.SetRSAPrivateKey([]byte(_pem)); err != nil {
|
||||||
|
panic("unable to set private key: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keychain implements the ClientPublickey interface
|
||||||
|
type keychain struct {
|
||||||
|
keys []*rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keychain) Key(i int) (interface{}, error) {
|
||||||
|
if i < 0 || i >= len(k.keys) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return k.keys[i].PublicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
|
||||||
|
hashFunc := crypto.SHA1
|
||||||
|
h := hashFunc.New()
|
||||||
|
h.Write(data)
|
||||||
|
digest := h.Sum()
|
||||||
|
return rsa.SignPKCS1v15(rand, k.keys[i], hashFunc, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keychain) loadPEM(file string) error {
|
||||||
|
buf, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(buf)
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("ssh: no key found")
|
||||||
|
}
|
||||||
|
r, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.keys = append(k.keys, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *rsa.PrivateKey
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
pkey, err = rsa.GenerateKey(rand.Reader, 512)
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to generate public key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientAuthPublickey(t *testing.T) {
|
||||||
|
k := new(keychain)
|
||||||
|
k.keys = append(k.keys, pkey)
|
||||||
|
|
||||||
|
serverConfig.PubKeyCallback = func(user, algo string, pubkey []byte) bool {
|
||||||
|
expected := []byte(serializePublickey(k.keys[0].PublicKey))
|
||||||
|
algoname := algoName(k.keys[0].PublicKey)
|
||||||
|
return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
|
||||||
|
}
|
||||||
|
serverConfig.PasswordCallback = nil
|
||||||
|
|
||||||
|
l, err := Listen("tcp", "0.0.0.0:0", serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to listen: %s", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Handshake(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := &ClientConfig{
|
||||||
|
User: "testuser",
|
||||||
|
Auth: []ClientAuth{
|
||||||
|
ClientAuthPublickey(k),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := Dial("tcp", l.Addr().String(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to dial remote side: %s", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// password implements the ClientPassword interface
|
||||||
|
type password string
|
||||||
|
|
||||||
|
func (p password) Password(user string) (string, error) {
|
||||||
|
return string(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientAuthPassword(t *testing.T) {
|
||||||
|
pw := password("tiger")
|
||||||
|
|
||||||
|
serverConfig.PasswordCallback = func(user, pass string) bool {
|
||||||
|
return user == "testuser" && pass == string(pw)
|
||||||
|
}
|
||||||
|
serverConfig.PubKeyCallback = nil
|
||||||
|
|
||||||
|
l, err := Listen("tcp", "0.0.0.0:0", serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to listen: %s", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Handshake(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := &ClientConfig{
|
||||||
|
User: "testuser",
|
||||||
|
Auth: []ClientAuth{
|
||||||
|
ClientAuthPassword(pw),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := Dial("tcp", l.Addr().String(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to dial remote side: %s", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientAuthPasswordAndPublickey(t *testing.T) {
|
||||||
|
pw := password("tiger")
|
||||||
|
|
||||||
|
serverConfig.PasswordCallback = func(user, pass string) bool {
|
||||||
|
return user == "testuser" && pass == string(pw)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := new(keychain)
|
||||||
|
k.keys = append(k.keys, pkey)
|
||||||
|
|
||||||
|
serverConfig.PubKeyCallback = func(user, algo string, pubkey []byte) bool {
|
||||||
|
expected := []byte(serializePublickey(k.keys[0].PublicKey))
|
||||||
|
algoname := algoName(k.keys[0].PublicKey)
|
||||||
|
return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := Listen("tcp", "0.0.0.0:0", serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to listen: %s", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Handshake(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
wrongPw := password("wrong")
|
||||||
|
config := &ClientConfig{
|
||||||
|
User: "testuser",
|
||||||
|
Auth: []ClientAuth{
|
||||||
|
ClientAuthPassword(wrongPw),
|
||||||
|
ClientAuthPublickey(k),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := Dial("tcp", l.Addr().String(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to dial remote side: %s", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
<-done
|
||||||
|
}
|
@ -5,6 +5,8 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/rsa"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@ -127,3 +129,86 @@ func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *ke
|
|||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serialize a signed slice according to RFC 4254 6.6.
|
||||||
|
func serializeSignature(algoname string, sig []byte) []byte {
|
||||||
|
length := stringLength([]byte(algoname))
|
||||||
|
length += stringLength(sig)
|
||||||
|
|
||||||
|
ret := make([]byte, length)
|
||||||
|
r := marshalString(ret, []byte(algoname))
|
||||||
|
r = marshalString(r, sig)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize an rsa.PublicKey or dsa.PublicKey according to RFC 4253 6.6.
|
||||||
|
func serializePublickey(key interface{}) []byte {
|
||||||
|
algoname := algoName(key)
|
||||||
|
switch key := key.(type) {
|
||||||
|
case rsa.PublicKey:
|
||||||
|
e := new(big.Int).SetInt64(int64(key.E))
|
||||||
|
length := stringLength([]byte(algoname))
|
||||||
|
length += intLength(e)
|
||||||
|
length += intLength(key.N)
|
||||||
|
ret := make([]byte, length)
|
||||||
|
r := marshalString(ret, []byte(algoname))
|
||||||
|
r = marshalInt(r, e)
|
||||||
|
marshalInt(r, key.N)
|
||||||
|
return ret
|
||||||
|
case dsa.PublicKey:
|
||||||
|
length := stringLength([]byte(algoname))
|
||||||
|
length += intLength(key.P)
|
||||||
|
length += intLength(key.Q)
|
||||||
|
length += intLength(key.G)
|
||||||
|
length += intLength(key.Y)
|
||||||
|
ret := make([]byte, length)
|
||||||
|
r := marshalString(ret, []byte(algoname))
|
||||||
|
r = marshalInt(r, key.P)
|
||||||
|
r = marshalInt(r, key.Q)
|
||||||
|
r = marshalInt(r, key.G)
|
||||||
|
marshalInt(r, key.Y)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
panic("unexpected key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func algoName(key interface{}) string {
|
||||||
|
switch key.(type) {
|
||||||
|
case rsa.PublicKey:
|
||||||
|
return "ssh-rsa"
|
||||||
|
case dsa.PublicKey:
|
||||||
|
return "ssh-dss"
|
||||||
|
}
|
||||||
|
panic("unexpected key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||||
|
// posession of a private key. See RFC 4252, section 7.
|
||||||
|
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||||
|
user := []byte(req.User)
|
||||||
|
service := []byte(req.Service)
|
||||||
|
method := []byte(req.Method)
|
||||||
|
|
||||||
|
length := stringLength(sessionId)
|
||||||
|
length += 1
|
||||||
|
length += stringLength(user)
|
||||||
|
length += stringLength(service)
|
||||||
|
length += stringLength(method)
|
||||||
|
length += 1
|
||||||
|
length += stringLength(algo)
|
||||||
|
length += stringLength(pubKey)
|
||||||
|
|
||||||
|
ret := make([]byte, length)
|
||||||
|
r := marshalString(ret, sessionId)
|
||||||
|
r[0] = msgUserAuthRequest
|
||||||
|
r = r[1:]
|
||||||
|
r = marshalString(r, user)
|
||||||
|
r = marshalString(r, service)
|
||||||
|
r = marshalString(r, method)
|
||||||
|
r[0] = 1
|
||||||
|
r = r[1:]
|
||||||
|
r = marshalString(r, algo)
|
||||||
|
r = marshalString(r, pubKey)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
@ -221,7 +221,7 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha
|
|||||||
return nil, nil, errors.New("internal error")
|
return nil, nil, errors.New("internal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedSig := serializeRSASignature(sig)
|
serializedSig := serializeSignature(hostAlgoRSA, sig)
|
||||||
|
|
||||||
kexDHReply := kexDHReplyMsg{
|
kexDHReply := kexDHReplyMsg{
|
||||||
HostKey: serializedHostKey,
|
HostKey: serializedHostKey,
|
||||||
@ -234,50 +234,9 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func serializeRSASignature(sig []byte) []byte {
|
|
||||||
length := stringLength([]byte(hostAlgoRSA))
|
|
||||||
length += stringLength(sig)
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, []byte(hostAlgoRSA))
|
|
||||||
r = marshalString(r, sig)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverVersion is the fixed identification string that Server will use.
|
// serverVersion is the fixed identification string that Server will use.
|
||||||
var serverVersion = []byte("SSH-2.0-Go\r\n")
|
var serverVersion = []byte("SSH-2.0-Go\r\n")
|
||||||
|
|
||||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
|
||||||
// posession of a private key. See RFC 4252, section 7.
|
|
||||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
|
||||||
user := []byte(req.User)
|
|
||||||
service := []byte(req.Service)
|
|
||||||
method := []byte(req.Method)
|
|
||||||
|
|
||||||
length := stringLength(sessionId)
|
|
||||||
length += 1
|
|
||||||
length += stringLength(user)
|
|
||||||
length += stringLength(service)
|
|
||||||
length += stringLength(method)
|
|
||||||
length += 1
|
|
||||||
length += stringLength(algo)
|
|
||||||
length += stringLength(pubKey)
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, sessionId)
|
|
||||||
r[0] = msgUserAuthRequest
|
|
||||||
r = r[1:]
|
|
||||||
r = marshalString(r, user)
|
|
||||||
r = marshalString(r, service)
|
|
||||||
r = marshalString(r, method)
|
|
||||||
r[0] = 1
|
|
||||||
r = r[1:]
|
|
||||||
r = marshalString(r, algo)
|
|
||||||
r = marshalString(r, pubKey)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake performs an SSH transport and client authentication on the given ServerConn.
|
// Handshake performs an SSH transport and client authentication on the given ServerConn.
|
||||||
func (s *ServerConn) Handshake() error {
|
func (s *ServerConn) Handshake() error {
|
||||||
var magics handshakeMagics
|
var magics handshakeMagics
|
||||||
|
Loading…
Reference in New Issue
Block a user