Commit d19d5ada authored by rui.zheng's avatar rui.zheng

#92 support ssh tunnel for local/remote port forwarding

parent f31949d1
...@@ -5,13 +5,15 @@ import ( ...@@ -5,13 +5,15 @@ import (
"fmt" "fmt"
"github.com/ginuerzh/gosocks5" "github.com/ginuerzh/gosocks5"
"github.com/golang/glog" "github.com/golang/glog"
"golang.org/x/crypto/ssh"
"net" "net"
"time" "time"
) )
type TcpForwardServer struct { type TcpForwardServer struct {
Base *ProxyServer Base *ProxyServer
Handler func(conn net.Conn, raddr net.Addr) sshClient *ssh.Client
Handler func(conn net.Conn, raddr *net.TCPAddr)
} }
func NewTcpForwardServer(base *ProxyServer) *TcpForwardServer { func NewTcpForwardServer(base *ProxyServer) *TcpForwardServer {
...@@ -34,19 +36,75 @@ func (s *TcpForwardServer) ListenAndServe() error { ...@@ -34,19 +36,75 @@ func (s *TcpForwardServer) ListenAndServe() error {
s.Handler = s.handleTcpForward s.Handler = s.handleTcpForward
} }
quit := make(chan interface{})
close(quit)
for { for {
start:
conn, err := ln.Accept() conn, err := ln.Accept()
if err != nil { if err != nil {
glog.V(LWARNING).Infoln(err) glog.V(LWARNING).Infoln("[tcp]", err)
continue continue
} }
setKeepAlive(conn, KeepAliveTime) setKeepAlive(conn, KeepAliveTime)
select {
case <-quit:
if s.Base.Chain.lastNode.Transport != "ssh" {
break
}
if err := s.initSSHClient(); err != nil {
glog.V(LWARNING).Infoln("[tcp]", err)
conn.Close()
goto start
}
quit = make(chan interface{})
go func(ch chan interface{}) {
s.sshClient.Wait()
glog.V(LINFO).Infoln("[tcp] connection closed")
close(ch)
}(quit)
default:
}
go s.Handler(conn, raddr) go s.Handler(conn, raddr)
} }
} }
func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr net.Addr) { func (s *TcpForwardServer) initSSHClient() error {
if s.sshClient != nil {
s.sshClient.Close()
s.sshClient = nil
}
sshNode := s.Base.Chain.lastNode
c, err := s.Base.Chain.GetConn()
if err != nil {
return err
}
var user, password string
if len(sshNode.Users) > 0 {
user = sshNode.Users[0].Username()
password, _ = sshNode.Users[0].Password()
}
config := ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
}
sshConn, chans, reqs, err := ssh.NewClientConn(c, sshNode.Addr, &config)
if err != nil {
return err
}
s.sshClient = ssh.NewClient(sshConn, chans, reqs)
s.Handler = s.handleTcpForwardSSH
return nil
}
func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr *net.TCPAddr) {
defer conn.Close() defer conn.Close()
glog.V(LINFO).Infof("[tcp] %s - %s", conn.RemoteAddr(), raddr) glog.V(LINFO).Infof("[tcp] %s - %s", conn.RemoteAddr(), raddr)
...@@ -62,6 +120,25 @@ func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr net.Addr) { ...@@ -62,6 +120,25 @@ func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr net.Addr) {
glog.V(LINFO).Infof("[tcp] %s >-< %s", conn.RemoteAddr(), raddr) glog.V(LINFO).Infof("[tcp] %s >-< %s", conn.RemoteAddr(), raddr)
} }
func (s *TcpForwardServer) handleTcpForwardSSH(conn net.Conn, raddr *net.TCPAddr) {
defer conn.Close()
if s.sshClient == nil {
return
}
rc, err := s.sshClient.DialTCP("tcp", nil, raddr)
if err != nil {
glog.V(LWARNING).Infof("[tcp] %s -> %s : %s", conn.RemoteAddr(), raddr, err)
return
}
defer rc.Close()
glog.V(LINFO).Infof("[tcp] %s <-> %s", conn.RemoteAddr(), raddr)
Transport(conn, rc)
glog.V(LINFO).Infof("[tcp] %s >-< %s", conn.RemoteAddr(), raddr)
}
type packet struct { type packet struct {
srcAddr string // src address srcAddr string // src address
dstAddr string // dest address dstAddr string // dest address
...@@ -348,16 +425,73 @@ func (s *RTcpForwardServer) Serve() error { ...@@ -348,16 +425,73 @@ func (s *RTcpForwardServer) Serve() error {
} }
retry = 0 retry = 0
if err := s.connectRTcpForward(conn, laddr, raddr); err != nil { glog.V(LINFO).Infof("[rtcp] %s - %s", laddr, raddr)
conn.Close()
time.Sleep(6 * time.Second) lastNode := s.Base.Chain.lastNode
if lastNode.Transport == "ssh" {
s.connectRTcpForwardSSH(conn, lastNode, laddr, raddr)
} else {
if err := s.connectRTcpForward(conn, laddr, raddr); err != nil {
conn.Close()
}
} }
time.Sleep(3 * time.Second)
} }
} }
func (s *RTcpForwardServer) connectRTcpForward(conn net.Conn, laddr, raddr net.Addr) error { func (s *RTcpForwardServer) connectRTcpForwardSSH(conn net.Conn, sshNode *ProxyNode, laddr, raddr net.Addr) error {
glog.V(LINFO).Infof("[rtcp] %s - %s", laddr, raddr) defer conn.Close()
var user, password string
if len(sshNode.Users) > 0 {
user = sshNode.Users[0].Username()
password, _ = sshNode.Users[0].Password()
}
config := ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
}
c, chans, reqs, err := ssh.NewClientConn(conn, sshNode.Addr, &config)
if err != nil {
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err)
return err
}
client := ssh.NewClient(c, chans, reqs)
defer client.Close()
ln, err := client.Listen("tcp", laddr.String())
if err != nil {
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err)
return err
}
defer ln.Close()
for {
rc, err := ln.Accept()
if err != nil {
return err
}
go func(c net.Conn) {
defer c.Close()
tc, err := net.DialTimeout("tcp", raddr.String(), time.Second*30)
if err != nil {
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err)
return
}
defer tc.Close()
glog.V(3).Infof("[rtcp] %s <-> %s", c.RemoteAddr(), c.LocalAddr())
Transport(c, tc)
glog.V(3).Infof("[rtcp] %s >-< %s", c.RemoteAddr(), c.LocalAddr())
}(rc)
}
}
func (s *RTcpForwardServer) connectRTcpForward(conn net.Conn, laddr, raddr net.Addr) error {
req := gosocks5.NewRequest(gosocks5.CmdBind, ToSocksAddr(laddr)) req := gosocks5.NewRequest(gosocks5.CmdBind, ToSocksAddr(laddr))
if err := req.Write(conn); err != nil { if err := req.Write(conn); err != nil {
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err)
...@@ -394,7 +528,7 @@ func (s *RTcpForwardServer) connectRTcpForward(conn net.Conn, laddr, raddr net.A ...@@ -394,7 +528,7 @@ func (s *RTcpForwardServer) connectRTcpForward(conn net.Conn, laddr, raddr net.A
go func() { go func() {
defer conn.Close() defer conn.Close()
lconn, err := net.DialTimeout("tcp", raddr.String(), time.Second*180) lconn, err := net.DialTimeout("tcp", raddr.String(), time.Second*30)
if err != nil { if err != nil {
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", rep.Addr, raddr, err) glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", rep.Addr, raddr, err)
return return
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"github.com/golang/glog" "github.com/golang/glog"
"io"
"net" "net"
"strings" "strings"
"time" "time"
...@@ -144,3 +145,18 @@ func basicProxyAuth(proxyAuth string) (username, password string, ok bool) { ...@@ -144,3 +145,18 @@ func basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
return cs[:s], cs[s+1:], true return cs[:s], cs[s+1:], true
} }
func Transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1)
go func() {
_, err := io.Copy(rw1, rw2)
errc <- err
}()
go func() {
_, err := io.Copy(rw2, rw1)
errc <- err
}()
return <-errc
}
...@@ -46,7 +46,6 @@ func (s *HttpServer) HandleRequest(req *http.Request) { ...@@ -46,7 +46,6 @@ func (s *HttpServer) HandleRequest(req *http.Request) {
valid := false valid := false
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
glog.V(LINFO).Infoln(u, p)
for _, user := range s.Base.Node.Users { for _, user := range s.Base.Node.Users {
username := user.Username() username := user.Username()
password, _ := user.Password() password, _ := user.Password()
......
...@@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { ...@@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
} }
switch node.Transport { switch node.Transport {
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht": case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht", "ssh":
case "https": case "https":
node.Protocol = "http" node.Protocol = "http"
node.Transport = "tls" node.Transport = "tls"
......
...@@ -7,7 +7,9 @@ import ( ...@@ -7,7 +7,9 @@ import (
"github.com/ginuerzh/gosocks5" "github.com/ginuerzh/gosocks5"
"github.com/golang/glog" "github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"golang.org/x/crypto/ssh"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
...@@ -125,6 +127,31 @@ func (s *ProxyServer) Serve() error { ...@@ -125,6 +127,31 @@ func (s *ProxyServer) Serve() error {
return NewShadowUdpServer(s, ttl).ListenAndServe() return NewShadowUdpServer(s, ttl).ListenAndServe()
case "pht": // pure http tunnel case "pht": // pure http tunnel
return NewPureHttpServer(s).ListenAndServe() return NewPureHttpServer(s).ListenAndServe()
case "ssh": // SSH tunnel
key := s.Node.Get("key")
privateBytes, err := ioutil.ReadFile(key)
if err != nil {
glog.V(LWARNING).Infoln("[ssh]", err)
privateBytes = defaultRawKey
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return err
}
config := ssh.ServerConfig{
PasswordCallback: DefaultPasswordCallback(s.Node.Users),
}
if len(s.Node.Users) == 0 {
config.NoClientAuth = true
}
config.AddHostKey(private)
s := &SSHServer{
Addr: node.Addr,
Base: s,
Config: &config,
}
return s.ListenAndServe()
default: default:
ln, err = net.Listen("tcp", node.Addr) ln, err = net.Listen("tcp", node.Addr)
} }
......
// The ssh tunnel is inspired by easyssh(https://dev.justinjudd.org/justin/easyssh)
package gost
import (
"encoding/binary"
"fmt"
"github.com/golang/glog"
"golang.org/x/crypto/ssh"
"net"
"net/url"
"strconv"
)
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
const (
DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
)
type SSHServer struct {
Addr string
Base *ProxyServer
Config *ssh.ServerConfig
Handler func(ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request)
}
func (s *SSHServer) ListenAndServe() error {
ln, err := net.Listen("tcp", s.Addr)
if err != nil {
glog.V(1).Infoln("[ssh] Listen:", err)
return err
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
glog.V(1).Infoln("[ssh] Accept:", err)
return err
}
go func(conn net.Conn) {
sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.Config)
if err != nil {
glog.V(1).Infof("[ssh] %s -> %s : %s", conn.RemoteAddr(), s.Addr, err)
return
}
defer sshConn.Close()
if s.Handler == nil {
s.Handler = s.handleSSHConn
}
glog.V(3).Infof("[ssh] %s <-> %s", conn.RemoteAddr(), s.Addr)
s.Handler(sshConn, chans, reqs)
glog.V(3).Infof("[ssh] %s >-< %s", conn.RemoteAddr(), s.Addr)
}(conn)
}
}
func (s *SSHServer) handleSSHConn(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
quit := make(chan interface{})
go func() {
for req := range reqs {
switch req.Type {
case RemoteForwardRequest:
go s.tcpipForwardRequest(conn, req, quit)
default:
if req.WantReply {
req.Reply(false, nil)
}
}
}
}()
go func() {
for newChannel := range chans {
// Check the type of channel
t := newChannel.ChannelType()
switch t {
case DirectForwardRequest:
channel, requests, err := newChannel.Accept()
if err != nil {
glog.V(3).Infoln("[ssh] Could not accept channel:", err)
continue
}
p := directForward{}
ssh.Unmarshal(newChannel.ExtraData(), &p)
go ssh.DiscardRequests(requests)
go s.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1))
default:
glog.V(3).Infoln("[ssh] Unknown channel type:", t)
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
}
}
}()
conn.Wait()
close(quit)
}
// directForward is structure for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
type directForward struct {
Host1 string
Port1 uint32
Host2 string
Port2 uint32
}
func (p directForward) String() string {
return fmt.Sprintf("%s:%d -> %s:%d", p.Host2, p.Port2, p.Host1, p.Port1)
}
func (s *SSHServer) directPortForwardChannel(channel ssh.Channel, raddr string) {
defer channel.Close()
glog.V(3).Infof("[ssh-tcp] %s - %s", s.Addr, raddr)
conn, err := s.Base.Chain.Dial(raddr)
if err != nil {
glog.V(3).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err)
return
}
defer conn.Close()
glog.V(3).Infof("[ssh-tcp] %s <-> %s", s.Addr, raddr)
Transport(conn, channel)
glog.V(3).Infof("[ssh-tcp] %s >-< %s", s.Addr, raddr)
}
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
type tcpipForward struct {
Host string
Port uint32
}
func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan interface{}) {
t := tcpipForward{}
ssh.Unmarshal(req.Payload, &t)
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
glog.V(3).Infoln("[ssh-rtcp] listening tcp", addr)
ln, err := net.Listen("tcp", addr) //tie to the client connection
if err != nil {
glog.V(1).Infoln("[ssh-rtcp]", err)
req.Reply(false, nil)
return
}
defer ln.Close()
replyFunc := func() error {
if t.Port == 0 && req.WantReply { // Client sent port 0. let them know which port is actually being used
_, port, err := getHostPortFromAddr(ln.Addr())
if err != nil {
return err
}
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(port))
t.Port = uint32(port)
return req.Reply(true, b[:])
}
return req.Reply(true, nil)
}
if err := replyFunc(); err != nil {
glog.V(1).Infoln("[ssh-rtcp]", err)
return
}
go func() {
for {
conn, err := ln.Accept()
if err != nil { // Unable to accept new connection - listener likely closed
return
}
go func(conn net.Conn) {
defer conn.Close()
p := directForward{}
var err error
var portnum int
p.Host1 = t.Host
p.Port1 = t.Port
p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr())
if err != nil {
return
}
p.Port2 = uint32(portnum)
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p))
if err != nil {
glog.V(1).Infoln("[ssh-rtcp] open forwarded channel:", err)
return
}
defer ch.Close()
go ssh.DiscardRequests(reqs)
glog.V(3).Infof("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
Transport(ch, conn)
glog.V(3).Infof("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
}(conn)
}
}()
<-quit
}
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
host, portString, err := net.SplitHostPort(addr.String())
if err != nil {
return
}
port, err = strconv.Atoi(portString)
return
}
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
func DefaultPasswordCallback(users []*url.Userinfo) PasswordCallbackFunc {
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
for _, user := range users {
u := user.Username()
p, _ := user.Password()
if u == conn.User() && p == string(password) {
return nil, nil
}
}
glog.V(3).Infof("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
return nil, fmt.Errorf("password rejected for %s", conn.User())
}
}
// Copyright 2016 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 ed25519 implements the Ed25519 signature algorithm. See
// http://ed25519.cr.yp.to/.
//
// These functions are also compatible with the “Ed25519” function defined in
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
package ed25519
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
import (
"crypto"
cryptorand "crypto/rand"
"crypto/sha512"
"crypto/subtle"
"errors"
"io"
"strconv"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
SignatureSize = 64
)
// PublicKey is the type of Ed25519 public keys.
type PublicKey []byte
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
type PrivateKey []byte
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[32:])
return PublicKey(publicKey)
}
// Sign signs the given message with priv.
// Ed25519 performs two passes over messages to be signed and therefore cannot
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
// indicate the message hasn't been hashed. This can be achieved by passing
// crypto.Hash(0) as the value for opts.
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
if opts.HashFunc() != crypto.Hash(0) {
return nil, errors.New("ed25519: cannot sign hashed message")
}
return Sign(priv, message), nil
}
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
if rand == nil {
rand = cryptorand.Reader
}
privateKey = make([]byte, PrivateKeySize)
publicKey = make([]byte, PublicKeySize)
_, err = io.ReadFull(rand, privateKey[:32])
if err != nil {
return nil, nil, err
}
digest := sha512.Sum512(privateKey[:32])
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
var A edwards25519.ExtendedGroupElement
var hBytes [32]byte
copy(hBytes[:], digest[:])
edwards25519.GeScalarMultBase(&A, &hBytes)
var publicKeyBytes [32]byte
A.ToBytes(&publicKeyBytes)
copy(privateKey[32:], publicKeyBytes[:])
copy(publicKey, publicKeyBytes[:])
return publicKey, privateKey, nil
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) []byte {
if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}
h := sha512.New()
h.Write(privateKey[:32])
var digest1, messageDigest, hramDigest [64]byte
var expandedSecretKey [32]byte
h.Sum(digest1[:0])
copy(expandedSecretKey[:], digest1[:])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
h.Reset()
h.Write(digest1[32:])
h.Write(message)
h.Sum(messageDigest[:0])
var messageDigestReduced [32]byte
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
var R edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
var encodedR [32]byte
R.ToBytes(&encodedR)
h.Reset()
h.Write(encodedR[:])
h.Write(privateKey[32:])
h.Write(message)
h.Sum(hramDigest[:0])
var hramDigestReduced [32]byte
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
var s [32]byte
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
signature := make([]byte, SignatureSize)
copy(signature[:], encodedR[:])
copy(signature[32:], s[:])
return signature
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool {
if l := len(publicKey); l != PublicKeySize {
panic("ed25519: bad public key length: " + strconv.Itoa(l))
}
if len(sig) != SignatureSize || sig[63]&224 != 0 {
return false
}
var A edwards25519.ExtendedGroupElement
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
if !A.FromBytes(&publicKeyBytes) {
return false
}
edwards25519.FeNeg(&A.X, &A.X)
edwards25519.FeNeg(&A.T, &A.T)
h := sha512.New()
h.Write(sig[:32])
h.Write(publicKey[:])
h.Write(message)
var digest [64]byte
h.Sum(digest[:0])
var hReduced [32]byte
edwards25519.ScReduce(&hReduced, &digest)
var R edwards25519.ProjectiveGroupElement
var b [32]byte
copy(b[:], sig[32:])
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
var checkR [32]byte
R.ToBytes(&checkR)
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
}
// 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 ssh
import (
"io"
"sync"
)
// buffer provides a linked list buffer for data exchange
// between producer and consumer. Theoretically the buffer is
// of unlimited capacity as it does no allocation of its own.
type buffer struct {
// protects concurrent access to head, tail and closed
*sync.Cond
head *element // the buffer that will be read first
tail *element // the buffer that will be read last
closed bool
}
// An element represents a single link in a linked list.
type element struct {
buf []byte
next *element
}
// newBuffer returns an empty buffer that is not closed.
func newBuffer() *buffer {
e := new(element)
b := &buffer{
Cond: newCond(),
head: e,
tail: e,
}
return b
}
// write makes buf available for Read to receive.
// buf must not be modified after the call to write.
func (b *buffer) write(buf []byte) {
b.Cond.L.Lock()
e := &element{buf: buf}
b.tail.next = e
b.tail = e
b.Cond.Signal()
b.Cond.L.Unlock()
}
// eof closes the buffer. Reads from the buffer once all
// the data has been consumed will receive os.EOF.
func (b *buffer) eof() error {
b.Cond.L.Lock()
b.closed = true
b.Cond.Signal()
b.Cond.L.Unlock()
return nil
}
// Read reads data from the internal buffer in buf. Reads will block
// if no data is available, or until the buffer is closed.
func (b *buffer) Read(buf []byte) (n int, err error) {
b.Cond.L.Lock()
defer b.Cond.L.Unlock()
for len(buf) > 0 {
// if there is data in b.head, copy it
if len(b.head.buf) > 0 {
r := copy(buf, b.head.buf)
buf, b.head.buf = buf[r:], b.head.buf[r:]
n += r
continue
}
// if there is a next buffer, make it the head
if len(b.head.buf) == 0 && b.head != b.tail {
b.head = b.head.next
continue
}
// if at least one byte has been copied, return
if n > 0 {
break
}
// if nothing was read, and there is nothing outstanding
// check to see if the buffer is closed.
if b.closed {
err = io.EOF
break
}
// out of buffers, wait for producer
b.Cond.Wait()
}
return
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// 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 (
"errors"
"fmt"
"net"
"sync"
"time"
)
// Client implements a traditional SSH client that supports shells,
// subprocesses, port forwarding and tunneled dialing.
type Client struct {
Conn
forwards forwardList // forwarded tcpip connections from the remote side
mu sync.Mutex
channelHandlers map[string]chan NewChannel
}
// HandleChannelOpen returns a channel on which NewChannel requests
// for the given type are sent. If the type already is being handled,
// nil is returned. The channel is closed when the connection is closed.
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
c.mu.Lock()
defer c.mu.Unlock()
if c.channelHandlers == nil {
// The SSH channel has been closed.
c := make(chan NewChannel)
close(c)
return c
}
ch := c.channelHandlers[channelType]
if ch != nil {
return nil
}
ch = make(chan NewChannel, chanSize)
c.channelHandlers[channelType] = ch
return ch
}
// NewClient creates a Client on top of the given connection.
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn := &Client{
Conn: c,
channelHandlers: make(map[string]chan NewChannel, 1),
}
go conn.handleGlobalRequests(reqs)
go conn.handleChannelOpens(chans)
go func() {
conn.Wait()
conn.forwards.closeAll()
}()
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
return conn
}
// NewClientConn establishes an authenticated SSH connection using c
// as the underlying transport. The Request and NewChannel channels
// must be serviced or the connection will hang.
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config
fullConf.SetDefaults()
conn := &connection{
sshConn: sshConn{conn: c},
}
if err := conn.clientHandshake(addr, &fullConf); err != nil {
c.Close()
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
}
conn.mux = newMux(conn.transport)
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
}
// clientHandshake performs the client side key exchange. See RFC 4253 Section
// 7.
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
if config.ClientVersion != "" {
c.clientVersion = []byte(config.ClientVersion)
} else {
c.clientVersion = []byte(packageVersion)
}
var err error
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
if err != nil {
return err
}
c.transport = newClientTransport(
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
if err := c.transport.waitSession(); err != nil {
return err
}
c.sessionID = c.transport.getSessionID()
return c.clientAuthenticate(config)
}
// verifyHostKeySignature verifies the host key obtained in the key
// exchange.
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
sig, rest, ok := parseSignatureBody(result.Signature)
if len(rest) > 0 || !ok {
return errors.New("ssh: signature parse error")
}
return hostKey.Verify(result.H, sig)
}
// NewSession opens a new Session for this client. (A session is a remote
// execution of a program.)
func (c *Client) NewSession() (*Session, error) {
ch, in, err := c.OpenChannel("session", nil)
if err != nil {
return nil, err
}
return newSession(ch, in)
}
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
for r := range incoming {
// This handles keepalive messages and matches
// the behaviour of OpenSSH.
r.Reply(false, nil)
}
}
// handleChannelOpens channel open messages from the remote side.
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
for ch := range in {
c.mu.Lock()
handler := c.channelHandlers[ch.ChannelType()]
c.mu.Unlock()
if handler != nil {
handler <- ch
} else {
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
}
}
c.mu.Lock()
for _, ch := range c.channelHandlers {
close(ch)
}
c.channelHandlers = nil
c.mu.Unlock()
}
// Dial starts a client connection to the given SSH server. It is a
// convenience function that connects to the given network address,
// initiates the SSH handshake, and then sets up a Client. For access
// to incoming channels and requests, use net.Dial with NewClientConn
// instead.
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
conn, err := net.DialTimeout(network, addr, config.Timeout)
if err != nil {
return nil, err
}
c, chans, reqs, err := NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
return NewClient(c, chans, reqs), nil
}
// A ClientConfig structure is used to configure a Client. It must not be
// modified after having been passed to an SSH function.
type ClientConfig struct {
// Config contains configuration that is shared between clients and
// servers.
Config
// User contains the username to authenticate as.
User string
// Auth contains possible authentication methods to use with the
// server. Only the first instance of a particular RFC 4252 method will
// be used during authentication.
Auth []AuthMethod
// HostKeyCallback, if not nil, is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyCallback
// implies that all host keys are accepted.
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
// ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used.
ClientVersion string
// HostKeyAlgorithms lists the key types that the client will
// accept from the server as host key, in order of
// preference. If empty, a reasonable default is used. Any
// string returned from PublicKey.Type method may be used, or
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
HostKeyAlgorithms []string
// Timeout is the maximum amount of time for the TCP connection to establish.
//
// A Timeout of zero means no timeout.
Timeout time.Duration
}
This diff is collapsed.
This diff is collapsed.
// Copyright 2013 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 (
"fmt"
"net"
)
// OpenChannelError is returned if the other side rejects an
// OpenChannel request.
type OpenChannelError struct {
Reason RejectionReason
Message string
}
func (e *OpenChannelError) Error() string {
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
}
// ConnMetadata holds metadata for the connection.
type ConnMetadata interface {
// User returns the user ID for this connection.
User() string
// SessionID returns the sesson hash, also denoted by H.
SessionID() []byte
// ClientVersion returns the client's version string as hashed
// into the session ID.
ClientVersion() []byte
// ServerVersion returns the server's version string as hashed
// into the session ID.
ServerVersion() []byte
// RemoteAddr returns the remote address for this connection.
RemoteAddr() net.Addr
// LocalAddr returns the local address for this connection.
LocalAddr() net.Addr
}
// Conn represents an SSH connection for both server and client roles.
// Conn is the basis for implementing an application layer, such
// as ClientConn, which implements the traditional shell access for
// clients.
type Conn interface {
ConnMetadata
// SendRequest sends a global request, and returns the
// reply. If wantReply is true, it returns the response status
// and payload. See also RFC4254, section 4.
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
// OpenChannel tries to open an channel. If the request is
// rejected, it returns *OpenChannelError. On success it returns
// the SSH Channel and a Go channel for incoming, out-of-band
// requests. The Go channel must be serviced, or the
// connection will hang.
OpenChannel(name string, data []byte) (Channel, <-chan *Request, error)
// Close closes the underlying network connection
Close() error
// Wait blocks until the connection has shut down, and returns the
// error causing the shutdown.
Wait() error
// TODO(hanwen): consider exposing:
// RequestKeyChange
// Disconnect
}
// DiscardRequests consumes and rejects all requests from the
// passed-in channel.
func DiscardRequests(in <-chan *Request) {
for req := range in {
if req.WantReply {
req.Reply(false, nil)
}
}
}
// A connection represents an incoming connection.
type connection struct {
transport *handshakeTransport
sshConn
// The connection protocol.
*mux
}
func (c *connection) Close() error {
return c.sshConn.conn.Close()
}
// sshconn provides net.Conn metadata, but disallows direct reads and
// writes.
type sshConn struct {
conn net.Conn
user string
sessionID []byte
clientVersion []byte
serverVersion []byte
}
func dup(src []byte) []byte {
dst := make([]byte, len(src))
copy(dst, src)
return dst
}
func (c *sshConn) User() string {
return c.user
}
func (c *sshConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *sshConn) Close() error {
return c.conn.Close()
}
func (c *sshConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *sshConn) SessionID() []byte {
return dup(c.sessionID)
}
func (c *sshConn) ClientVersion() []byte {
return dup(c.clientVersion)
}
func (c *sshConn) ServerVersion() []byte {
return dup(c.serverVersion)
}
// 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 implements an SSH client and server.
SSH is a transport security protocol, an authentication protocol and a
family of application protocols. The most typical application level
protocol is a remote shell and this is specifically implemented. However,
the multiplexed nature of SSH is exposed to users that wish to support
others.
References:
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
*/
package ssh // import "golang.org/x/crypto/ssh"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// 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 ssh
// Message authentication support
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"hash"
)
type macMode struct {
keySize int
etm bool
new func(key []byte) hash.Hash
}
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
// a given size.
type truncatingMAC struct {
length int
hmac hash.Hash
}
func (t truncatingMAC) Write(data []byte) (int, error) {
return t.hmac.Write(data)
}
func (t truncatingMAC) Sum(in []byte) []byte {
out := t.hmac.Sum(in)
return out[:len(in)+t.length]
}
func (t truncatingMAC) Reset() {
t.hmac.Reset()
}
func (t truncatingMAC) Size() int {
return t.length
}
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
var macModes = map[string]*macMode{
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
return hmac.New(sha1.New, key)
}},
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
return truncatingMAC{12, hmac.New(sha1.New, key)}
}},
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -21,10 +21,10 @@ ...@@ -21,10 +21,10 @@
"revisionTime": "2017-01-19T05:34:58Z" "revisionTime": "2017-01-19T05:34:58Z"
}, },
{ {
"checksumSHA1": "/unEypznQ0qT7TWxnA4KLOgOXwo=", "checksumSHA1": "lSL+LZe/WK67zmkONjox+zr66qI=",
"path": "github.com/ginuerzh/gost", "path": "github.com/ginuerzh/gost",
"revision": "1e709ceababe54dc0254683304147359b9d59146", "revision": "f31949d1ed0ddf2ea3c930f2859528c3f146d48c",
"revisionTime": "2017-02-11T12:45:40Z" "revisionTime": "2017-02-11T13:17:58Z"
}, },
{ {
"checksumSHA1": "+XIOnTW0rv8Kr/amkXgMraNeUr4=", "checksumSHA1": "+XIOnTW0rv8Kr/amkXgMraNeUr4=",
...@@ -182,6 +182,18 @@ ...@@ -182,6 +182,18 @@
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-02-08T20:51:15Z"
}, },
{
"checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=",
"path": "golang.org/x/crypto/ed25519",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z"
},
{
"checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
"path": "golang.org/x/crypto/ed25519/internal/edwards25519",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z"
},
{ {
"checksumSHA1": "4D8hxMIaSDEW5pCQk22Xj4DcDh4=", "checksumSHA1": "4D8hxMIaSDEW5pCQk22Xj4DcDh4=",
"path": "golang.org/x/crypto/hkdf", "path": "golang.org/x/crypto/hkdf",
...@@ -206,6 +218,12 @@ ...@@ -206,6 +218,12 @@
"revision": "1150b8bd09e53aea1d415621adae9bad665061a1", "revision": "1150b8bd09e53aea1d415621adae9bad665061a1",
"revisionTime": "2016-10-21T22:59:10Z" "revisionTime": "2016-10-21T22:59:10Z"
}, },
{
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=",
"path": "golang.org/x/crypto/ssh",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z"
},
{ {
"checksumSHA1": "Iwv89z1aXYKaB936lnsmE2NcfqA=", "checksumSHA1": "Iwv89z1aXYKaB936lnsmE2NcfqA=",
"path": "golang.org/x/crypto/tea", "path": "golang.org/x/crypto/tea",
......
This diff is collapsed.
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"github.com/golang/glog" "github.com/golang/glog"
"io"
"net" "net"
"strings" "strings"
"time" "time"
...@@ -144,3 +145,18 @@ func basicProxyAuth(proxyAuth string) (username, password string, ok bool) { ...@@ -144,3 +145,18 @@ func basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
return cs[:s], cs[s+1:], true return cs[:s], cs[s+1:], true
} }
func Transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1)
go func() {
_, err := io.Copy(rw1, rw2)
errc <- err
}()
go func() {
_, err := io.Copy(rw2, rw1)
errc <- err
}()
return <-errc
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment