first version

master
fangdingjun 8 years ago
commit 233ba2732a

1
.gitignore vendored

@ -0,0 +1 @@
*~

@ -0,0 +1,166 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

@ -0,0 +1,91 @@
obfssh
=====
obfssh is wrapper for ssh protocol, use AES or RC4 to encrypt the transport data,
ssh is a good designed protocol and with the good encryption, but the protocol has a especially figerprint,
the firewall can easily identify the protocol and block it or QOS it, especial when we use its port forward function to escape from the state censorship.
obfssh encrypt the ssh protocol and hide the figerprint, the firewall can not identify the protocol.
We borrow the idea from https://github.com/brl/obfuscated-openssh, but not compatible with it,
beause the limitions of golang ssh library.
server usage example
====================
import "github.com/fangdingjun/obfssh"
import "golang.org/x/crypto/ssh"
// key for encryption
obfs_key := "some keyword"
// encrypt method
obfs_method := "rc4"
config := &ssh.ServerConfig{
// add ssh server configure here
// for example auth method, cipher, MAC
...
}
l, err := net.Listen(":2022")
c, err := l.Accept()
sc, err := obfssh.NewServer(c, config, obfs_method, obfs_key)
sc.Run()
client usage example
====================
import "github.com/fangdingjun/obfssh"
import "golang.org/x/crypto/ssh"
addr := "localhost:2022"
// key for encryption
obfs_key := "some keyword"
// encrypt method
obfs_method := "rc4"
config := ssh.ClientConfig{
// add ssh client config here
// for example auth method
...
}
c, err := net.Dial("tcp", addr)
// create connection
client, err := obfssh.NewClient(c, config, addr, obfs_method, obfs_key)
// local to remote port forward
client.AddLocalForward(":2234:10.0.0.1:3221")
// remote to local port forward
client.AddRemoteForward(":2234:10.2.0.1:3221")
// dynamic port forward
client.AddDynamicForward(":4321")
// wait to be done
client.Run()
limitions
========
now, the server side only implements the port forward function, start shell or execute a command is not suppurted
if set the `obfs_method` to `none`, obfssh is compatible with standard ssh server/client(OpenSSH)
License
=======
GPLv3, see LICENSE file details

@ -0,0 +1,288 @@
package obfssh
import (
socks "github.com/fangdingjun/socks-go"
"github.com/golang/crypto/ssh"
"github.com/golang/crypto/ssh/terminal"
"net"
"os"
"os/signal"
"syscall"
"time"
)
// Client is ssh client connection
type Client struct {
conn net.Conn
sshConn ssh.Conn
client *ssh.Client
listeners []net.Listener
ch chan int
}
// NewClient create a new ssh Client
//
// addr is server address
//
// method is obfs encrypt method, value is rc4, aes or none or ""
//
// key is obfs encrypt key
//
// if set method to none or "", means disable the obfs,
// when the obfs is disabled, the client can connect to standard ssh server, like OpenSSH server
//
func NewClient(c net.Conn, config *ssh.ClientConfig, addr, method, key string) (*Client, error) {
Log(DEBUG, "create obfs conn with method %s", method)
obfsConn, err := NewObfsConn(&TimedOutConn{c, 15 * time.Second}, method, key, false)
if err != nil {
return nil, err
}
sshConn, newch, reqs, err := ssh.NewClientConn(obfsConn, addr, config)
if err != nil {
return nil, err
}
sshClient := ssh.NewClient(sshConn, newch, reqs)
client := &Client{
conn: c, sshConn: sshConn, client: sshClient,
ch: make(chan int),
}
go client.keepAlive(10*time.Second, 4)
return client, nil
}
// Run wait ssh connection to finish
func (cc *Client) Run() {
select {
case <-time.After(1 * time.Second):
}
// wait port forward to finish
if cc.listeners != nil {
Log(DEBUG, "wait all channel to be done")
go cc.registerSignal()
go func() {
cc.sshConn.Wait()
select {
case cc.ch <- 1:
default:
}
}()
// wait exit signal
select {
case <-cc.ch:
Log(INFO, "got signal, exit")
}
}
Log(DEBUG, "Done")
cc.Close()
}
// Close close the ssh connection
// and free all the port forward resources
func (cc *Client) Close() {
for _, l := range cc.listeners {
Log(INFO, "close the listener %s", l.Addr())
l.Close()
}
//Log(DEBUG, "close ssh connection")
//cc.sshConn.Close()
}
// RunCmd run a single command on server
func (cc *Client) RunCmd(cmd string) ([]byte, error) {
Log(INFO, "run command %s", cmd)
session, err := cc.client.NewSession()
if err != nil {
Log(DEBUG, "command exited with error: %s", err.Error())
} else {
Log(DEBUG, "command exited with no error")
}
if err != nil {
return nil, err
}
d, err := session.CombinedOutput(cmd)
session.Close()
return d, err
}
// Shell start a login shell on server
func (cc *Client) Shell() error {
Log(DEBUG, "request new session")
session, err := cc.client.NewSession()
if err != nil {
return err
}
session.Stdin = os.Stdin
session.Stdout = os.Stdout
session.Stderr = os.Stderr
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
// this make CTRL+C works
Log(DEBUG, "turn terminal mode to raw")
oldState, _ := terminal.MakeRaw(0)
w, h, _ := terminal.GetSize(0)
Log(DEBUG, "request pty")
if err := session.RequestPty("xterm", h, w, modes); err != nil {
Log(ERROR, "request pty error: %s", err.Error())
Log(DEBUG, "restore terminal mode")
terminal.Restore(0, oldState)
return err
}
Log(DEBUG, "request shell")
if err := session.Shell(); err != nil {
Log(ERROR, "start shell error: %s", err.Error())
Log(DEBUG, "restore terminal mode")
terminal.Restore(0, oldState)
return err
}
session.Wait()
Log(DEBUG, "session closed")
terminal.Restore(0, oldState)
Log(DEBUG, "restore terminal mode")
return nil
}
// AddLocalForward add a local to remote port forward
func (cc *Client) AddLocalForward(local, remote string) error {
Log(DEBUG, "add local forward %s -> %s", local, remote)
l, err := net.Listen("tcp", local)
if err != nil {
return err
}
cc.listeners = append(cc.listeners, l)
go func(l net.Listener) {
//defer l.Close()
for {
c, err := l.Accept()
if err != nil {
Log(DEBUG, "local listen %s closed", l.Addr())
return
}
Log(DEBUG, "connection accepted from %s", c.RemoteAddr())
go cc.handleLocalForward(c, remote)
}
}(l)
return nil
}
// AddRemoteForward add a remote to local port forward
func (cc *Client) AddRemoteForward(local, remote string) error {
Log(DEBUG, "add remote forward %s -> %s", remote, local)
l, err := cc.client.Listen("tcp", remote)
if err != nil {
return err
}
cc.listeners = append(cc.listeners, l)
go func(l net.Listener) {
//defer l.Close()
for {
c, err := l.Accept()
if err != nil {
Log(DEBUG, "remote listener %s closed", l.Addr())
return
}
Log(DEBUG, "accept remote forward connection from %s", c.RemoteAddr())
go cc.handleRemoteForward(c, local)
}
}(l)
return nil
}
// AddDynamicForward add a dynamic port forward
func (cc *Client) AddDynamicForward(local string) error {
Log(DEBUG, "add dynamic forward %s", local)
l, err := net.Listen("tcp", local)
if err != nil {
return err
}
cc.listeners = append(cc.listeners, l)
go func(l net.Listener) {
//defer l.Close()
for {
c, err := l.Accept()
if err != nil {
Log(DEBUG, "local listener %s closed", l.Addr())
return
}
Log(DEBUG, "accept connection from %s", c.RemoteAddr())
go cc.handleDynamicForward(c)
}
}(l)
return nil
}
func (cc *Client) handleLocalForward(conn net.Conn, remote string) {
rconn, err := cc.client.Dial("tcp", remote)
if err != nil {
Log(ERROR, "connect to %s failed: %s", remote, err.Error())
conn.Close()
return
}
Log(DEBUG, "remote connect to %s success", remote)
PipeAndClose(rconn, conn)
}
func (cc *Client) handleRemoteForward(conn net.Conn, local string) {
lconn, err := net.Dial("tcp", local)
if err != nil {
Log(ERROR, "connect to %s failed: %s", local, err.Error())
conn.Close()
return
}
Log(DEBUG, "connect to %s success", local)
PipeAndClose(conn, lconn)
}
func (cc *Client) handleDynamicForward(conn net.Conn) {
s := socks.SocksConn{conn, cc.client.Dial}
s.Serve()
}
func (cc *Client) keepAlive(interval time.Duration, maxCount int) {
count := 0
c := time.NewTicker(interval)
for {
select {
case <-c.C:
_, _, err := cc.sshConn.SendRequest("keepalive@openssh.org", true, nil)
if err != nil {
Log(DEBUG, "keep alive error: %s", err.Error())
count++
} else {
count = 0
}
if count >= maxCount {
Log(ERROR, "keep alive hit max count, exit")
cc.sshConn.Close()
// send exit signal
select {
case cc.ch <- 1:
default:
}
return
}
}
}
}
func (cc *Client) registerSignal() {
c := make(chan os.Signal, 5)
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
select {
case s1 := <-c:
Log(ERROR, "signal %d received, exit", s1)
select {
case cc.ch <- 1:
default:
}
}
}

@ -0,0 +1,284 @@
package obfssh
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"crypto/sha1"
"crypto/sha512"
"encoding/binary"
"errors"
"io"
//"log"
"math/big"
"net"
"strings"
"time"
)
const (
keyLength = 16
seedLength = 16
maxPadding = 1024
magicValue uint32 = 0x0BF5CA7E
loopCount = 10
)
// ObfsConn implement the net.Conn interface which enrytp/decrpt
// the data automatic
type ObfsConn struct {
net.Conn
key []byte
cipherRead cipher.Stream
cipherWrite cipher.Stream
cipherDisabled bool
method string
writeBuf []byte
writeBufLen int
//isServer bool
}
// NewObfsConn initial a ObfsConn
// after new return, seed handshake is done
func NewObfsConn(c net.Conn, method, key string, isServer bool) (*ObfsConn, error) {
wc := &ObfsConn{
Conn: c,
key: []byte(key),
cipherDisabled: false,
method: method,
writeBuf: make([]byte, 8192),
writeBufLen: 8192,
// isServer: isServer,
}
// do not initial chiper when encrypt method is empty or none
if method == "" || method == "none" {
wc.DisableObfs()
return wc, nil
}
if isServer {
if err := wc.readSeed(); err != nil {
buf := make([]byte, 1024)
Log(DEBUG, "read forever")
// read forever
for {
if _, err1 := wc.Conn.Read(buf); err1 != nil {
return nil, err
}
}
return nil, err
}
} else {
if err := wc.writeSeed(); err != nil {
return nil, err
}
}
return wc, nil
}
func generateKey(seed, keyword, iv []byte) []byte {
buf := make([]byte, seedLength+len(keyword)+len(iv))
copy(buf[0:], seed)
// user key
if keyword != nil {
copy(buf[seedLength:], keyword)
}
copy(buf[seedLength+len(keyword):], iv)
o := sha512.Sum512(buf[0:])
for i := 0; i < loopCount; i++ {
o = sha512.Sum512(o[0:])
}
return o[0:keyLength]
}
// EnableObfs enable the encryption
func (wc *ObfsConn) EnableObfs() {
Log(DEBUG, "enable the encryption")
wc.cipherDisabled = false
}
// DisableObfs disable the encryption
func (wc *ObfsConn) DisableObfs() {
Log(DEBUG, "disable the encryption")
wc.cipherDisabled = true
}
func (wc *ObfsConn) writeSeed() error {
Log(DEBUG, "begin to write the seed")
ii, err := rand.Int(rand.Reader, big.NewInt(int64(maxPadding)))
if err != nil {
//Log(ERROR, "initial the random seed failed: %s", err.Error())
return err
}
i := ii.Int64()
Log(DEBUG, "use padding data length %d\n", int(i))
buf := make([]byte, seedLength+8+int(i))
// generate seed
rand.Read(buf[0:seedLength])
// put magic value
binary.BigEndian.PutUint32(buf[seedLength:seedLength+4], magicValue)
// put padding length
binary.BigEndian.PutUint32(buf[seedLength+4:seedLength+8], uint32(i))
// generate padding data
rand.Read(buf[24:])
// generate the key
keyToServer := generateKey(buf[0:seedLength], wc.key, []byte("client_to_server"))
keyToClient := generateKey(buf[0:seedLength], wc.key, []byte("server_to_client"))
var r, w cipher.Stream
// initial the cipher
switch strings.ToLower(wc.method) {
case "aes":
w, r = newAESCipher(keyToServer, keyToClient)
case "rc4":
w, r = newRC4Cipher(keyToServer, keyToClient)
default:
return errors.New("unknown cipher type")
}
wc.cipherWrite = w
wc.cipherRead = r
// encrypt the data, except the seed
wc.cipherWrite.XORKeyStream(buf[seedLength:], buf[seedLength:])
_, err = wc.Conn.Write(buf[0:])
if err != nil {
return err
}
Log(DEBUG, "write seed done")
return nil
}
func (wc *ObfsConn) readSeed() error {
Log(DEBUG, "begin to read the seed")
buf := make([]byte, seedLength+8)
// read the data except padding
_, err := io.ReadFull(wc.Conn, buf)
if err != nil {
return err
}
// generate the key
keyToServer := generateKey(buf[0:seedLength], wc.key, []byte("client_to_server"))
keyToClient := generateKey(buf[0:seedLength], wc.key, []byte("server_to_client"))
var w, r cipher.Stream
switch strings.ToLower(wc.method) {
case "aes":
w, r = newAESCipher(keyToClient, keyToServer)
case "rc4":
w, r = newRC4Cipher(keyToClient, keyToServer)
}
wc.cipherWrite = w
wc.cipherRead = r
// decrypt the magic and padding length
wc.cipherRead.XORKeyStream(buf[seedLength:seedLength+8], buf[seedLength:seedLength+8])
// check magic value
magic := binary.BigEndian.Uint32(buf[seedLength : seedLength+4])
if magic != magicValue {
Log(ERROR, "magic %x check failed from %s", magic, wc.Conn.RemoteAddr())
return errors.New("wrong magic value")
}
// read the padding data
padLen := binary.BigEndian.Uint32(buf[seedLength+4 : seedLength+8])
Log(DEBUG, "padding %d", padLen)
buf = make([]byte, padLen)
if _, err := io.ReadFull(wc, buf[0:]); err != nil {
return err
}
Log(DEBUG, "read seed done")
return nil
}
// Read read the data from underlying connection
// if encryption enabled, decrypt the data and return to plain data to upstream
func (wc *ObfsConn) Read(buf []byte) (int, error) {
n, err := wc.Conn.Read(buf)
if err != nil {
return 0, err
}
if !wc.cipherDisabled {
wc.cipherRead.XORKeyStream(buf[0:n], buf[0:n])
}
//log.Printf("%+q", buf[0:n])
return n, err
}
// Write write the data to underlying connection
// if encryption enabled, encrypt it before write
func (wc *ObfsConn) Write(buf []byte) (int, error) {
if !wc.cipherDisabled {
bufLen := len(buf)
if bufLen > wc.writeBufLen {
wc.writeBufLen = bufLen + 8192
wc.writeBuf = make([]byte, wc.writeBufLen)
}
wc.cipherWrite.XORKeyStream(wc.writeBuf[0:bufLen], buf[0:bufLen])
return wc.Conn.Write(wc.writeBuf[0:bufLen])
}
return wc.Conn.Write(buf[0:])
}
func newAESCipher(key1, key2 []byte) (cipher.Stream, cipher.Stream) {
b1, _ := aes.NewCipher(key1)
b2, _ := aes.NewCipher(key2)
m1 := sha1.Sum(key1)
iv1 := md5.Sum(m1[0:])
m2 := sha1.Sum(key2)
iv2 := md5.Sum(m2[0:])
w := cipher.NewCFBEncrypter(b1, iv1[0:])
r := cipher.NewCFBDecrypter(b2, iv2[0:])
return w, r
}
func newRC4Cipher(key1, key2 []byte) (cipher.Stream, cipher.Stream) {
w, _ := rc4.NewCipher(key1)
r, _ := rc4.NewCipher(key2)
return w, r
}
// TimedOutConn is a net.Conn with read/write timeout set
type TimedOutConn struct {
net.Conn
Timeout time.Duration
}
func (tc *TimedOutConn) Read(b []byte) (int, error) {
tc.Conn.SetDeadline(time.Now().Add(tc.Timeout))
return tc.Conn.Read(b)
}
func (tc *TimedOutConn) Write(b []byte) (int, error) {
tc.Conn.SetDeadline(time.Now().Add(tc.Timeout))
return tc.Conn.Write(b)
}

@ -0,0 +1,75 @@
package obfssh
/*
obfssh is wrapper for ssh protocol, use AES or RC4 to encrypt the transport data,
ssh is a good designed protocol and with the good encryption, but the protocol has a especially figerprint,
the firewall can easily identify the protocol and block it or QOS it, especial when we use its port forward function to escape from the state censorship.
obfssh encrypt the ssh protocol and hide the figerprint, the firewall can not identify the protocol.
We borrow the idea from https://github.com/brl/obfuscated-openssh, but not compatible with it,
beause the limitions of golang ssh library.
server usage example
import "github.com/fangdingjun/obfssh"
import "golang.org/x/crypto/ssh"
// key for encryption
obfs_key := "some keyword"
// encrypt method
obfs_method := "rc4"
config := &ssh.ServerConfig{
// add ssh server configure here
// for example auth method, cipher, MAC
...
}
l, err := net.Listen(":2022")
c, err := l.Accept()
sc, err := obfssh.NewServer(c, config, obfs_method, obfs_key)
sc.Run()
client usage example
import "github.com/fangdingjun/obfssh"
import "golang.org/x/crypto/ssh"
addr := "localhost:2022"
// key for encryption
obfs_key := "some keyword"
// encrypt method
obfs_method := "rc4"
config := ssh.ClientConfig{
// add ssh client config here
// for example auth method
...
}
c, err := net.Dial("tcp", addr)
// create connection
client, err := obfssh.NewClient(c, config, addr, obfs_method, obfs_key)
// local to remote port forward
client.AddLocalForward(":2234:10.0.0.1:3221")
// remote to local port forward
client.AddRemoteForward(":2234:10.2.0.1:3221")
// dynamic port forward
client.AddDynamicForward(":4321")
// wait to be done
client.Run()
*/

@ -0,0 +1,2 @@
obfssh_client*
run.bat

@ -0,0 +1,29 @@
obfssh\_client
=============
this is obfssh\_client example
usage
=====
run server
go get github.com/fangdingjun/obfssh/obfssh_server
cp $GOPATH/src/github.com/fangdingjun/obfssh/obfssh_server/config_example.yaml config.yaml
vim config.yaml
ssh-keygen -f ssh_host_rsa_key -t rsa 1024
$GOPATH/bin/obfssh_server -c config.yaml
run client
go get github.com/fangdingjun/obfssh/obfssh_client
$GOPATH/bin/obfssh_client -N -D :1234 -obfs_key some_keyworld -obfs_method rc4 -p 2022 -l user2 -pw user2 localhost
this will create a socks proxy on :1234

@ -0,0 +1,228 @@
package main
import (
//"bytes"
"flag"
"fmt"
"github.com/fangdingjun/obfssh"
"github.com/golang/crypto/ssh"
"github.com/golang/crypto/ssh/agent"
//"github.com/golang/crypto/ssh/terminal"
"time"
//"io"
"io/ioutil"
"log"
"net"
"os"
//"os/signal"
"path/filepath"
"strings"
//"sync"
//"syscall"
)
var method, encryptKey string
type stringSlice []string
func (lf *stringSlice) Set(val string) error {
*lf = append(*lf, val)
return nil
}
func (lf *stringSlice) String() string {
s := ""
if lf == nil {
return s
}
for _, v := range *lf {
s += " "
s += v
}
return s
}
var localForwards stringSlice
var remoteForwards stringSlice
var dynamicForwards stringSlice
func main() {
var host, port, user, pass, key string
//var localForward, remoteForward, dynamicForward string
var notRunCmd bool
var debug bool
flag.StringVar(&user, "l", os.Getenv("USER"), "ssh username")
flag.StringVar(&pass, "pw", "", "ssh password")
flag.StringVar(&port, "p", "22", "remote port")
flag.StringVar(&key, "i", "", "private key file")
flag.Var(&localForwards, "L", "forward local port to remote, format [local_host:]local_port:remote_host:remote_port")
flag.Var(&remoteForwards, "R", "forward remote port to local, format [remote_host:]remote_port:local_host:local_port")
flag.BoolVar(&notRunCmd, "N", false, "not run remote command, useful when do port forward")
flag.Var(&dynamicForwards, "D", "enable dynamic forward, format [local_host:]local_port")
flag.StringVar(&method, "obfs_method", "", "transport encrypt method, avaliable: rc4, aes, empty means disable encrypt")
flag.StringVar(&encryptKey, "obfs_key", "", "transport encrypt key")
flag.BoolVar(&debug, "d", false, "verbose mode")
flag.Parse()
if debug {
obfssh.SSHLogLevel = obfssh.DEBUG
}
auth := []ssh.AuthMethod{}
// read ssh agent and default auth key
if pass == "" && key == "" {
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
obfssh.Log(obfssh.DEBUG, "add auth method with agent %s", os.Getenv("SSH_AUTH_SOCK"))
auth = append(auth, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
home := os.Getenv("HOME")
for _, f := range []string{
".ssh/id_rsa",
".ssh/id_dsa",
".ssh/identity",
".ssh/id_ecdsa",
".ssh/id_ed25519",
} {
k1 := filepath.Join(home, f)
if pemBytes, err := ioutil.ReadFile(k1); err == nil {
if priKey, err := ssh.ParsePrivateKey(pemBytes); err == nil {
obfssh.Log(obfssh.DEBUG, "add private key: %s", k1)
auth = append(auth, ssh.PublicKeys(priKey))
}
}
}
}
args := flag.Args()
var cmd string
switch len(args) {
case 0:
flag.PrintDefaults()
log.Fatal("you must specify the remote host")
case 1:
host = args[0]
cmd = ""
default:
host = args[0]
cmd = strings.Join(args[1:], " ")
}
if strings.Contains(host, "@") {
ss := strings.SplitN(host, "@", 2)
user = ss[0]
host = ss[1]
}
if pass != "" {
obfssh.Log(obfssh.DEBUG, "add password auth method")
auth = append(auth, ssh.Password(pass))
}
if key != "" {
pemBytes, err := ioutil.ReadFile(key)
if err != nil {
log.Fatal(err)
}
priKey, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
log.Fatal(err)
}
obfssh.Log(obfssh.DEBUG, "add private key %s", key)
auth = append(auth, ssh.PublicKeys(priKey))
}
config := &ssh.ClientConfig{
User: user,
Auth: auth,
Timeout: 10 * time.Second,
}
h := net.JoinHostPort(host, port)
c, err := net.Dial("tcp", h)
if err != nil {
log.Fatal(err)
}
client, err := obfssh.NewClient(c, config, h, method, encryptKey)
if err != nil {
log.Fatal(err)
}
var local, remote string
for _, p := range localForwards {
addr := parseForwardAddr(p)
if len(addr) != 4 && len(addr) != 3 {
log.Printf("wrong forward addr %s, format: [local_host:]local_port:remote_host:remote_port", p)
continue
}
if len(addr) == 4 {
local = strings.Join(addr[:2], ":")
remote = strings.Join(addr[2:], ":")
} else {
local = fmt.Sprintf(":%s", addr[0])
remote = strings.Join(addr[1:], ":")
}
//log.Printf("add local to remote %s->%s", local, remote)
if err := client.AddLocalForward(local, remote); err != nil {
log.Println(err)
}
}
for _, p := range remoteForwards {
addr := parseForwardAddr(p)
if len(addr) != 4 && len(addr) != 3 {
log.Printf("wrong forward addr %s, format: [local_host:]local_port:remote_host:remote_port", p)
continue
}
if len(addr) == 4 {
remote = strings.Join(addr[:2], ":")
local = strings.Join(addr[2:], ":")
} else {
remote = fmt.Sprintf("0.0.0.0:%s", addr[0])
local = strings.Join(addr[1:], ":")
}
//log.Printf("add remote to local %s->%s", remote, local)
if err := client.AddRemoteForward(local, remote); err != nil {
log.Println(err)
}
}
for _, p := range dynamicForwards {
if strings.Index(p, ":") == -1 {
local = fmt.Sprintf(":%s", p)
} else {
local = p
}
//log.Printf("listen on %s", local)
if err := client.AddDynamicForward(local); err != nil {
log.Println(err)
}
}
if !notRunCmd {
if cmd != "" {
if d, err := client.RunCmd(cmd); err != nil {
log.Println(err)
} else {
//log.Printf("%s", string(d))
fmt.Printf("%s", string(d))
}
} else {
if err := client.Shell(); err != nil {
log.Println(err)
}
}
}
client.Run()
}
func parseForwardAddr(s string) []string {
ss := strings.FieldsFunc(s, func(c rune) bool {
if c == ':' {
return true
}
return false
})
return ss
}

@ -0,0 +1,2 @@
obfssh_server*
authorized_keys

@ -0,0 +1,29 @@
obfssh\_server
=============
this is obfssh\_server example
usage
=====
run server
go get github.com/fangdingjun/obfssh/obfssh_server
cp $GOPATH/src/github.com/fangdingjun/obfssh/obfssh_server/config_example.yaml config.yaml
vim config.yaml
ssh-keygen -f ssh_host_rsa_key -t rsa 1024
$GOPATH/bin/obfssh_server -c config.yaml
run client
go get github.com/fangdingjun/obfssh/obfssh_client
$GOPATH/bin/obfssh_client -N -D :1234 -obfs_key some_keyworld -obfs_method rc4 -p 2022 -l user2 -pw user2 localhost
this will create a socks proxy on :1234

@ -0,0 +1,72 @@
package main
import (
"bytes"
"github.com/go-yaml/yaml"
"github.com/golang/crypto/ssh"
"io/ioutil"
"log"
)
type serverConfig struct {
Port int `yaml:"port"`
Key string `yaml:"obfs_key"`
Debug bool `yaml:"debug"`
HostKey string `yaml:"host_key_file"`
Method string `yaml:"obfs_method"`
Users []serverUser `yaml:"users"`
}
type serverUser struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
AuthorizedKeyFile string `yaml:"authorized_key_file"`
publicKeys []ssh.PublicKey
}
func (c *serverConfig) getUser(user string) (serverUser, error) {
for _, u := range c.Users {
if u.Username == user {
return u, nil
}
}
return serverUser{}, nil
}
func loadConfig(f string) (*serverConfig, error) {
buf, err := ioutil.ReadFile(f)
if err != nil {
return nil, err
}
var c serverConfig
if err := yaml.Unmarshal(buf, &c); err != nil {
return nil, err
}
for i := range c.Users {
buf1, err := ioutil.ReadFile(c.Users[i].AuthorizedKeyFile)
if err != nil {
log.Printf("read publickey for %s failed, ignore", c.Users[i].Username)
continue
}
// parse authorized_key
//var err error
var p1 ssh.PublicKey
r := bytes.Trim(buf1, " \r\n")
for {
p1, _, _, r, err = ssh.ParseAuthorizedKey(r)
if err != nil {
//log.Println(err)
//log.Printf("%+v %+v", r, p1)
return nil, err
}
c.Users[i].publicKeys = append(c.Users[i].publicKeys, p1)
if len(r) == 0 {
break
}
}
}
return &c, nil
}

@ -0,0 +1,46 @@
# vim: set ft=yaml:
#
#
# listen port
port: 2022
# the key to encrypt the transport data
obfs_key: some_keyword
# ssh host key file
host_key_file: ssh_host_rsa_key
# the method to encrypt the transport data
# avaiable methods: rc4, aes, none or ""
# none or "" means disable the obfs encrypt
#obfs_method: rc4
obfs_method: "rc4"
# show more log message
# value true or false
debug: true
# the users used by ssh server
# user can authorite by passwrod or by public key
# public key as same as OpenSSH
# public key or password must be specify one
#
users:
-
# username
username: user1
# password, empty password means disable password authorize
password: ""
# public key file
authorized_key_file: /path/to/user/authorized_keys
-
username: user2
password: user2
authorized_key_file: /path/to/user/authorized_keys
-
username: user3
password: ""
authorized_key_file: /path/to/authorized_keys

@ -0,0 +1,16 @@
package main
import (
//"github.com/go-yaml/yaml"
//"io/ioutil"
"log"
"testing"
)
func TestConfig(t *testing.T) {
c, err := loadConfig("config_example.yaml")
if err != nil {
log.Fatal(err)
}
log.Printf("%+v", c)
}

@ -0,0 +1,99 @@
package main
import (
"bytes"
"flag"
"fmt"
"github.com/fangdingjun/obfssh"
"github.com/golang/crypto/ssh"
"io/ioutil"
"log"
"net"
)
func main() {
var configfile string
flag.StringVar(&configfile, "c", "config.yaml", "configure file")
flag.Parse()
conf, err := loadConfig(configfile)
if err != nil {
log.Fatal(err)
}
// set log level
if conf.Debug {
obfssh.SSHLogLevel = obfssh.DEBUG
}
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if u, err := conf.getUser(c.User()); err == nil {
if u.Password != "" && c.User() == u.Username && string(pass) == u.Password {
return nil, nil
}
}
return nil, fmt.Errorf("password reject for user %s", c.User())
},
PublicKeyCallback: func(c ssh.ConnMetadata, k ssh.PublicKey) (*ssh.Permissions, error) {
if u, err := conf.getUser(c.User()); err == nil {
for _, pk := range u.publicKeys {
if k.Type() == pk.Type() && bytes.Compare(k.Marshal(), pk.Marshal()) == 0 {
return nil, nil
}
}
}
return nil, fmt.Errorf("publickey reject for user %s", c.User())
},
// auth log
AuthLogCallback: func(c ssh.ConnMetadata, method string, err error) {
if err != nil {
obfssh.Log(obfssh.ERROR, "%s", err.Error())
obfssh.Log(obfssh.ERROR, "%s auth failed for %s from %s", method, c.User(), c.RemoteAddr())
} else {
obfssh.Log(obfssh.INFO, "Accepted %s for user %s from %s", method, c.User(), c.RemoteAddr())
}
},
}
privateBytes, err := ioutil.ReadFile(conf.HostKey)
if err != nil {
log.Fatal(err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal(err)
}
config.AddHostKey(private)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.Port))
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
c, err := l.Accept()
if err != nil {
fmt.Println(err)
return
}
obfssh.Log(obfssh.DEBUG, "accept tcp connection from %s", c.RemoteAddr())
go func(c net.Conn) {
sc, err := obfssh.NewServer(c, config, conf.Method, conf.Key)
if err != nil {
c.Close()
obfssh.Log(obfssh.ERROR, "%s", err.Error())
return
}
sc.Run()
}(c)
}
}

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA2n/6710+F060gDjqmHXWwBgKKKhykVVH91PcBW9txzCG2HHr
dM0RV7GmC703TVTaJZjHb8xWQCLrUorDEdabKte49QrsM5F6tjTqPG+LEazFUmyI
EH8/B4ISIKRzW1+G75dFx28CTC8Zr1Ue6PJRDuBJYOJA104EuIxS3fCNiQU0oD/A
lkbfl90FmFjth47Pu/hdHN+kbfOA9TO2cpztgjz+6vEd9JjNuc9xR83okfXPjI4i
RvjPk4K70LGCGyPkIjbK1vu5zDffwmROGi9BBOR2X17LnCsez2GDP6paz7sIwOOU
kikqxm2fjSfOjS1kBB0Yc2o3w7kZ4CTHO/U6BwIDAQABAoIBAQCflpg2Wjkultq5
SFj4cCEg/q300kuToOFGYSazhZZ9xRDIaDRchCclkOhBbLtGrTIEAdmw62MXxylv
iVA+6Cs/GH7L42VvqNMi3/UxnRrLFxCsSf77ZaUom7UXyGVFGLmapzddGdRoxoIR
EK/Z3pqbre+KZtaLKz3BeSRRXBBUQXJopvvfh7UNr/wFQQmLzqFWUO8ommxr6J3w
a1JhVPS1MGmxn18teQyB6I9xtw347MAt7NQ1ZQXPbCjr/v1ZNt4bLQrae/S/jWNc
4/WmxnK4fAhHNdBjofNKa+SLbcoDkHtUBZquh418oPNE6BUn14qocKlUPASYS+5E
zC5UCxzRAoGBAPRylOjt5OTAhl5vMC7EFESK8Wzma2WBZnrjkP+u8NFOLo3cRcvj
+jsTO1ilKR7PQJfeU/NBWrIU+wgm22nMGlayHAXrtbueN9/devURbOb7md75AGvS
DB0mB4Acuxqn4a2ThTjDi+H6+Oqz0Ab7ps3Wm6cFH0ZRlREE0woAljmJAoGBAOTT
eVkJmSzNLqEDtKuBdysTJp+j0hhUediWh8rallvo5e9zMrXJksfAstHJcR8NkqLD
VL9K4Vydudw70efPg8siSyNDUjDXCy18Vl3x+xqIXsFVXGybTpLIGj22irKX8ZQZ
qpZhHBbFw1terQCFdkAt+B6skJUU4zeiE1uQEUMPAoGAJwt2RY5aFT+7NrJD2/Rt
2FTpIx/a36e/mrlmm7BxvrziKr6YV2zetzjnLc2Tt9wa0Sct+Zjix7caMb8jJM75
Fgf0+e0gZgtrmVJjJWnXHz3o4fib3Jz8WluMryXnrOZL4dHCYcK6QSo5QCPggn0H
s7Enw5HJ4Q1+5e0DWIGnfSECgYEAio1JkpHvP2NVcoUN5jLT9y73Wf4VfknYJT6w
JjHIjQot/5ifAdd1mqGhJMl2RzkuqoLfU5yBbFTMbv+Bj3zk7iBrooRmxc/PotEA
co3MXzpnNWT8O36mStYCnY9j19OMoQIRelB+c4N3UGG5GvG0shOjgt82BC7LjaoD
UpOfAB0CgYBHuda3elvCalfDevia3ZaKKw/JT0CS4J0DKkrWk97Yet/MaDE0k/wk
ammJCNckfGI5nH7m6Ljd6A5cKjL3i5TgvMUjNz8qb9f6GiK5fBxLzO3KhgnsoIft
r7VjCr/nYvvM4asYwSdoHA6P2XKtOsuB7XEFL4k2EJ3WvCyYcjMvOw==
-----END RSA PRIVATE KEY-----

@ -0,0 +1,300 @@
package obfssh
import (
"fmt"
"github.com/golang/crypto/ssh"
"github.com/golang/crypto/ssh/terminal"
//"log"
"net"
)
// Server is server connection
type Server struct {
conn net.Conn
sshConn *ssh.ServerConn
forwardedPorts map[string]net.Listener
exitCh chan int
}
// NewServer create a new struct for Server
//
// c is net.Conn
//
// config is &ssh.ServerConfig
//
// method is obfs encrypt method, value is rc4, aes or none or ""
//
// key is obfs encrypt key
//
// if set method to none or "", means disable obfs encryption, when the obfs is disabled,
// the server can accept connection from standard ssh client, like OpenSSH client
//
func NewServer(c net.Conn, config *ssh.ServerConfig, method, key string) (*Server, error) {
wc, err := NewObfsConn(c, method, key, true)
if err != nil {
return nil, err
}
sshConn, ch, req, err := ssh.NewServerConn(wc, config)
if err != nil {
return nil, err
}
//wc.DisableObfs()
sc := &Server{conn: c,
sshConn: sshConn,
forwardedPorts: map[string]net.Listener{},
exitCh: make(chan int)}
go sc.handleGlobalRequest(req)
go sc.handleNewChannelRequest(ch)
return sc, nil
}
// Run waits for server connection finish
func (sc *Server) Run() {
sc.sshConn.Wait()
Log(DEBUG, "ssh connection closed")
sc.close()
}
func (sc *Server) close() {
Log(DEBUG, "close connection")
sc.sshConn.Close()
//Log(DEBUG, "close listener")
for _, l := range sc.forwardedPorts {
Log(DEBUG, "close listener %s", l.Addr())
l.Close()
}
}
func (sc *Server) handleNewChannelRequest(ch <-chan ssh.NewChannel) {
for newch := range ch {
switch newch.ChannelType() {
case "session":
//go sc.handleSession(newch)
//continue
case "direct-tcpip":
go handleDirectTcpip(newch)
continue
}
Log(DEBUG, "reject channel request %s", newch.ChannelType())
newch.Reject(ssh.UnknownChannelType, "unknown channel type")
//channel, request, err := newch.Accept()
}
}
func (sc *Server) handleGlobalRequest(req <-chan *ssh.Request) {
for r := range req {
switch r.Type {
case "tcpip-forward":
Log(DEBUG, "request port forward")
go sc.handleTcpipForward(r)
continue
case "cancel-tcpip-forward":
Log(DEBUG, "request cancel port forward")
go sc.handleCancelTcpipForward(r)
continue
}
Log(DEBUG, "global request %s", r.Type)
if r.WantReply {
r.Reply(false, nil)
}
}
}
func (sc *Server) handleChannelRequest(req <-chan *ssh.Request) {
ret := false
for r := range req {
switch r.Type {
case "shell":
ret = true
case "pty-req":
ret = true
case "env":
ret = true
case "exec":
ret = false
case "subsystem":
default:
ret = false
}
if r.WantReply {
r.Reply(ret, nil)
}
}
}
type directTcpipMsg struct {
Raddr string
Rport uint32
Laddr string
Lport uint32
}
func (sc *Server) handleSession(newch ssh.NewChannel) {
ch, req, err := newch.Accept()
if err != nil {
Log(ERROR, "%s", err.Error())
return
}
go sc.handleChannelRequest(req)
term := terminal.NewTerminal(ch, "shell>")
defer ch.Close()
for {
line, err := term.ReadLine()
if err != nil {
break
}
term.Write([]byte(line))
term.Write([]byte("\n"))
}
}
func handleDirectTcpip(newch ssh.NewChannel) {
data := newch.ExtraData()
var r directTcpipMsg
err := ssh.Unmarshal(data, &r)
if err != nil {
Log(DEBUG, "invalid ssh parameter")
newch.Reject(ssh.ConnectionFailed, "invalid argument")
return
}
Log(DEBUG, "create connection to %s:%d", r.Raddr, r.Rport)
rconn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", r.Raddr, r.Rport))
if err != nil {
Log(ERROR, "%s", err.Error())
newch.Reject(ssh.ConnectionFailed, "invalid argument")
return
}
channel, requests, err := newch.Accept()
if err != nil {
rconn.Close()
Log(ERROR, "%s", err.Error())
return
}
//log.Println("forward")
go ssh.DiscardRequests(requests)
PipeAndClose(channel, rconn)
}
type tcpipForwardAddr struct {
Addr string
Port uint32
}
func (sc *Server) handleCancelTcpipForward(req *ssh.Request) {
var a tcpipForwardAddr
if err := ssh.Unmarshal(req.Payload, &a); err != nil {
Log(ERROR, "invalid ssh parameter for cancel port forward")
if req.WantReply {
req.Reply(false, nil)
}
return
}
k := fmt.Sprintf("%s:%d", a.Addr, a.Port)
if l, ok := sc.forwardedPorts[k]; ok {
l.Close()
delete(sc.forwardedPorts, k)
}
if req.WantReply {
req.Reply(true, nil)
}
}
func (sc *Server) handleTcpipForward(req *ssh.Request) {
var addr tcpipForwardAddr
if err := ssh.Unmarshal(req.Payload, &addr); err != nil {
Log(ERROR, "parse ssh data error: %s", err.Error)
if req.WantReply {
req.Reply(false, nil)
}
return
}
if addr.Port > 65535 || addr.Port < 0 {
Log(ERROR, "invalid port %d", addr.Port)
if req.WantReply {
req.Reply(false, nil)
}
return
}
ip := net.ParseIP(addr.Addr)
if ip == nil {
Log(ERROR, "invalid ip %d", addr.Port)
if req.WantReply {
req.Reply(false, nil)
}
return
}
k := fmt.Sprintf("%s:%d", addr.Addr, addr.Port)
if _, ok := sc.forwardedPorts[k]; ok {
// port in use
Log(ERROR, "port in use: %s", k)
if req.WantReply {
req.Reply(false, nil)
}
return
}
//Log(DEBUG, "get request for addr: %s, port: %d", addr.Addr, addr.Port)
l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: ip, Port: int(addr.Port)})
if err != nil {
// listen failed
Log(ERROR, "%s", err.Error())
if req.WantReply {
req.Reply(false, nil)
}
return
}
a1 := l.Addr()
Log(DEBUG, "Listening port %s", a1)
p := struct {
Port uint32
}{
uint32(a1.(*net.TCPAddr).Port),
}
sc.forwardedPorts[k] = l
if req.WantReply {
req.Reply(true, ssh.Marshal(p))
}
for {
c, err := l.Accept()
if err != nil {
Log(ERROR, "%s", err.Error())
return
}
Log(DEBUG, "accept connection from %s", c.RemoteAddr())
go func(c net.Conn) {
laddr := c.LocalAddr()
raddr := c.RemoteAddr()
a2 := struct {
laddr string
lport uint32
raddr string
rport uint32
}{
addr.Addr,
uint32(laddr.(*net.TCPAddr).Port),
raddr.(*net.TCPAddr).IP.String(),
uint32(raddr.(*net.TCPAddr).Port),
}
ch, r, err := sc.sshConn.OpenChannel("forwarded-tcpip", ssh.Marshal(a2))
if err != nil {
Log(ERROR, "forward port failed: %s", err.Error())
c.Close()
return
}
go ssh.DiscardRequests(r)
PipeAndClose(c, ch)
}(c)
}
}

@ -0,0 +1,45 @@
package obfssh
import (
"io"
"log"
)
const (
_ = iota
// DEBUG log level debug
DEBUG
// INFO log level info
INFO
// ERROR log level error
ERROR
)
// SSHLogLevel global value for log level
var SSHLogLevel = ERROR
// PipeAndClose pipe the data between c and s, close both when done
func PipeAndClose(c io.ReadWriteCloser, s io.ReadWriteCloser) {
defer c.Close()
defer s.Close()
cc := make(chan int, 2)
go func() {
io.Copy(c, s)
cc <- 1
}()
go func() {
io.Copy(s, c)
cc <- 1
}()
<-cc
}
// Log log the message by level
func Log(level int, s string, args ...interface{}) {
if level >= SSHLogLevel {
log.Printf(s, args...)
}
}
Loading…
Cancel
Save