first version
commit
233ba2732a
@ -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(¬RunCmd, "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…
Reference in New Issue