mirror of https://github.com/cbeuw/Cloak
Use ECDH instead of ECIES
This commit is contained in:
parent
b9f2aa4ed0
commit
a8786a5576
|
|
@ -112,13 +112,7 @@ func main() {
|
||||||
log.Printf("Starting standalone mode. Listening for ss on %v:%v\n", localHost, localPort)
|
log.Printf("Starting standalone mode. Listening for ss on %v:%v\n", localHost, localPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
sta := &client.State{
|
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
||||||
SS_LOCAL_HOST: localHost,
|
|
||||||
SS_LOCAL_PORT: localPort,
|
|
||||||
SS_REMOTE_HOST: remoteHost,
|
|
||||||
SS_REMOTE_PORT: remotePort,
|
|
||||||
Now: time.Now,
|
|
||||||
}
|
|
||||||
err := sta.ParseConfig(pluginOpts)
|
err := sta.ParseConfig(pluginOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ var version string
|
||||||
|
|
||||||
func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
|
func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
|
||||||
for {
|
for {
|
||||||
i, err := io.Copy(dst, src)
|
_, err := io.Copy(dst, src)
|
||||||
if err != nil || i == 0 {
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
go dst.Close()
|
go dst.Close()
|
||||||
go src.Close()
|
go src.Close()
|
||||||
return
|
return
|
||||||
|
|
@ -159,15 +160,7 @@ func main() {
|
||||||
localPort = strings.Split(*localAddr, ":")[1]
|
localPort = strings.Split(*localAddr, ":")[1]
|
||||||
log.Printf("Starting standalone mode, listening on %v:%v to ss at %v:%v\n", remoteHost, remotePort, localHost, localPort)
|
log.Printf("Starting standalone mode, listening on %v:%v to ss at %v:%v\n", remoteHost, remotePort, localHost, localPort)
|
||||||
}
|
}
|
||||||
sta := &server.State{
|
sta := server.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
||||||
SS_LOCAL_HOST: localHost,
|
|
||||||
SS_LOCAL_PORT: localPort,
|
|
||||||
SS_REMOTE_HOST: remoteHost,
|
|
||||||
SS_REMOTE_PORT: remotePort,
|
|
||||||
Now: time.Now,
|
|
||||||
UsedRandom: map[[32]byte]int{},
|
|
||||||
Sessions: map[[32]byte]*mux.Session{},
|
|
||||||
}
|
|
||||||
err := sta.ParseConfig(pluginOpts)
|
err := sta.ParseConfig(pluginOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Configuration file error: %v", err)
|
log.Fatalf("Configuration file error: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"ServerName":"www.bing.com",
|
"ServerName":"www.bing.com",
|
||||||
"Key":"UNhY4JhezH9gQYqvDMWrWH9CwlcKiECVqejMrND2VFwEOF8c8XRX8iYVdjKW2BAfym2zppExMPteovDB/Q8phdD53FnH39tQ1daaVLn9+FIGOAdk+UZZ2aOt5jSK638YPg==",
|
"Key":"UNhY4JhezH9gQYqvDMWrWH9CwlcKiECVqejMrND2VFy2wjljjjqJWGiNoAYpWscJ0VEVkewo6o8S/jcNdNxFLQ==",
|
||||||
"TicketTimeHint":3600,
|
"TicketTimeHint":3600,
|
||||||
"NumConn":4,
|
"NumConn":4,
|
||||||
"MaskBrowser":"chrome"
|
"MaskBrowser":"chrome"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"WebServerAddr":"204.79.197.200:443",
|
"WebServerAddr":"204.79.197.200:443",
|
||||||
"Key":"H2pMM834RzkouOoRGNhbiQRnm4Ggy8sg+S6ve5yYfqUEOF8c8XRX8iYVdjKW2BAfym2zppExMPteovDB/Q8phdD53FnH39tQ1daaVLn9+FIGOAdk+UZZ2aOt5jSK638YPg=="
|
"Key":"CN+VRP9OqZR0+Im2X/1y6FvaK7+GBnX6qCiovbo+eVo="
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,57 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
"github.com/cbeuw/ecies"
|
ecdh "github.com/cbeuw/go-ecdh"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeRandomField(sta *State) []byte {
|
type keyPair struct {
|
||||||
|
crypto.PrivateKey
|
||||||
|
crypto.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRandomField(sta *State) []byte {
|
||||||
t := make([]byte, 8)
|
t := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/12*60*60))
|
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/(12*60*60)))
|
||||||
rand := util.PsudoRandBytes(16, sta.Now().UnixNano())
|
rdm := make([]byte, 16)
|
||||||
|
io.ReadFull(rand.Reader, rdm)
|
||||||
preHash := make([]byte, 56)
|
preHash := make([]byte, 56)
|
||||||
copy(preHash[0:32], sta.SID)
|
copy(preHash[0:32], sta.SID)
|
||||||
copy(preHash[32:40], t)
|
copy(preHash[32:40], t)
|
||||||
copy(preHash[40:56], rand)
|
copy(preHash[40:56], rdm)
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
ret := make([]byte, 32)
|
ret := make([]byte, 32)
|
||||||
copy(ret[0:16], rand)
|
copy(ret[0:16], rdm)
|
||||||
copy(ret[16:32], h.Sum(nil)[0:16])
|
copy(ret[16:32], h.Sum(nil)[0:16])
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeSessionTicket(sta *State) []byte {
|
func MakeSessionTicket(sta *State) []byte {
|
||||||
t := make([]byte, 8)
|
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted SID 32 bytes][padding 128 bytes]
|
||||||
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/int64(sta.TicketTimeHint)))
|
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
|
||||||
plain := make([]byte, 40)
|
// for encrypting the SID
|
||||||
copy(plain, sta.SID)
|
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
||||||
copy(plain[32:], t)
|
ec := ecdh.NewCurve25519ECDH()
|
||||||
// With the default settings (P256, AES128, SHA256) of the ecies package, len(ct)==153.
|
ephKP := sta.getKeyPair(tthInterval)
|
||||||
//
|
if ephKP == nil {
|
||||||
// ciphertext is composed of 3 parts: marshalled X and Y coordinates on the curve,
|
ephPv, ephPub, _ := ec.GenerateKey(rand.Reader)
|
||||||
// iv+ciphertext of the block cipher (aes128 in this case),
|
ephKP = &keyPair{
|
||||||
// and the hmac which is 32 bytes because it's sha256
|
ephPv,
|
||||||
//
|
ephPub,
|
||||||
// The marshalling is done by crypto/elliptic.Marshal. According to the code,
|
}
|
||||||
// the size after marshall is 65
|
sta.putKeyPair(tthInterval, ephKP)
|
||||||
//
|
}
|
||||||
// IV is 16 bytes. The size of ciphertext is equal to the plaintext, which is 40,
|
ticket := make([]byte, 192)
|
||||||
// that is 32 bytes of SID + 8 bytes of timestamp/tickettimehint.
|
copy(ticket[0:32], ec.Marshal(ephKP.PublicKey))
|
||||||
// 16+40 = 56
|
key, _ := ec.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
|
||||||
//
|
cipherSID := util.AESEncrypt(ticket[0:16], key, sta.SID)
|
||||||
// Then the hmac is 32 bytes
|
copy(ticket[32:64], cipherSID)
|
||||||
//
|
io.ReadFull(rand.Reader, ticket[64:192])
|
||||||
// 65+56+32=153
|
return ticket
|
||||||
ct, _ := ecies.Encrypt(rand.Reader, sta.pub, plain, nil, nil)
|
|
||||||
sessionTicket := make([]byte, 192)
|
|
||||||
// The reason for ct[1:] is that, the first byte of ct is always 0x04
|
|
||||||
// This is specified in the section 4.3.6 of ANSI X9.62 (the uncompressed form).
|
|
||||||
// This is a flag that is useless to us and it will expose our pattern
|
|
||||||
// (because the sessionTicket isn't fully random anymore). Therefore we drop it.
|
|
||||||
copy(sessionTicket, ct[1:])
|
|
||||||
copy(sessionTicket[152:], util.PsudoRandBytes(40, sta.Now().UnixNano()))
|
|
||||||
return sessionTicket
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/cbeuw/ecies"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ecdh "github.com/cbeuw/go-ecdh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
|
|
@ -25,15 +27,31 @@ type State struct {
|
||||||
SS_LOCAL_PORT string
|
SS_LOCAL_PORT string
|
||||||
SS_REMOTE_HOST string
|
SS_REMOTE_HOST string
|
||||||
SS_REMOTE_PORT string
|
SS_REMOTE_PORT string
|
||||||
|
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
SID []byte
|
SID []byte
|
||||||
pub *ecies.PublicKey
|
staticPub crypto.PublicKey
|
||||||
|
keyPairsM sync.RWMutex
|
||||||
|
keyPairs map[int64]*keyPair
|
||||||
|
|
||||||
TicketTimeHint int
|
TicketTimeHint int
|
||||||
ServerName string
|
ServerName string
|
||||||
MaskBrowser string
|
MaskBrowser string
|
||||||
NumConn int
|
NumConn int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||||
|
ret := &State{
|
||||||
|
SS_LOCAL_HOST: localHost,
|
||||||
|
SS_LOCAL_PORT: localPort,
|
||||||
|
SS_REMOTE_HOST: remoteHost,
|
||||||
|
SS_REMOTE_PORT: remotePort,
|
||||||
|
Now: nowFunc,
|
||||||
|
}
|
||||||
|
ret.keyPairs = make(map[int64]*keyPair)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// semi-colon separated value. This is for Android plugin options
|
// semi-colon separated value. This is for Android plugin options
|
||||||
func ssvToJson(ssv string) (ret []byte) {
|
func ssvToJson(ssv string) (ret []byte) {
|
||||||
unescape := func(s string) string {
|
unescape := func(s string) string {
|
||||||
|
|
@ -89,24 +107,29 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
return errors.New("Failed to parse Key: " + err.Error())
|
return errors.New("Failed to parse Key: " + err.Error())
|
||||||
}
|
}
|
||||||
sta.SID = sid
|
sta.SID = sid
|
||||||
sta.pub = pub
|
sta.staticPub = pub
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structure: [SID 32 bytes][marshalled public key]
|
// Structure: [SID 32 bytes][marshalled public key 32 bytes]
|
||||||
func parseKey(b64 string) ([]byte, *ecies.PublicKey, error) {
|
func parseKey(b64 string) ([]byte, crypto.PublicKey, error) {
|
||||||
b, err := base64.StdEncoding.DecodeString(b64)
|
b, err := base64.StdEncoding.DecodeString(b64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
sid := b[0:32]
|
ec := ecdh.NewCurve25519ECDH()
|
||||||
marshalled := b[32:]
|
pub, _ := ec.Unmarshal(b[32:64])
|
||||||
x, y := elliptic.Unmarshal(ecies.DefaultCurve, marshalled)
|
return b[0:32], pub, nil
|
||||||
pub := &ecies.PublicKey{
|
}
|
||||||
X: x,
|
|
||||||
Y: y,
|
func (sta *State) getKeyPair(tthInterval int64) *keyPair {
|
||||||
Curve: ecies.DefaultCurve,
|
sta.keyPairsM.Lock()
|
||||||
Params: ecies.ParamsFromCurve(ecies.DefaultCurve),
|
defer sta.keyPairsM.Unlock()
|
||||||
}
|
return sta.keyPairs[tthInterval]
|
||||||
return sid, pub, nil
|
}
|
||||||
|
|
||||||
|
func (sta *State) putKeyPair(tthInterval int64, kp *keyPair) {
|
||||||
|
sta.keyPairsM.Lock()
|
||||||
|
sta.keyPairs[tthInterval] = kp
|
||||||
|
sta.keyPairsM.Unlock()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package multiplex
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -70,6 +71,7 @@ func (stream *Stream) Read(buf []byte) (n int, err error) {
|
||||||
func (stream *Stream) Write(in []byte) (n int, err error) {
|
func (stream *Stream) Write(in []byte) (n int, err error) {
|
||||||
select {
|
select {
|
||||||
case <-stream.die:
|
case <-stream.die:
|
||||||
|
log.Printf("Stream %v dying\n", stream.id)
|
||||||
return 0, errors.New(errBrokenPipe)
|
return 0, errors.New(errBrokenPipe)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +102,8 @@ func (stream *Stream) Write(in []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stream *Stream) Close() error {
|
func (stream *Stream) Close() error {
|
||||||
|
log.Printf("ID: %v closing\n", stream.id)
|
||||||
|
|
||||||
// Because closing a closed channel causes panic
|
// Because closing a closed channel causes panic
|
||||||
stream.closingM.Lock()
|
stream.closingM.Lock()
|
||||||
defer stream.closingM.Unlock()
|
defer stream.closingM.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,10 @@ type sentNotifier struct {
|
||||||
|
|
||||||
func (ce *connEnclave) send(data []byte) {
|
func (ce *connEnclave) send(data []byte) {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
n, _ := ce.remoteConn.Write(data)
|
n, err := ce.remoteConn.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
sn := &sentNotifier{
|
sn := &sentNotifier{
|
||||||
ce,
|
ce,
|
||||||
n,
|
n,
|
||||||
|
|
@ -121,7 +124,7 @@ func (sb *switchboard) dispatch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *switchboard) deplex(ce *connEnclave) {
|
func (sb *switchboard) deplex(ce *connEnclave) {
|
||||||
buf := make([]byte, 20480)
|
buf := make([]byte, 204800)
|
||||||
for {
|
for {
|
||||||
i, err := sb.session.obfsedReader(ce.remoteConn, buf)
|
i, err := sb.session.obfsedReader(ce.remoteConn, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -136,6 +139,7 @@ func (sb *switchboard) deplex(ce *connEnclave) {
|
||||||
stream = sb.session.addStream(frame.StreamID)
|
stream = sb.session.addStream(frame.StreamID)
|
||||||
}
|
}
|
||||||
if closing := sb.session.getStream(frame.ClosingStreamID); closing != nil {
|
if closing := sb.session.getStream(frame.ClosingStreamID); closing != nil {
|
||||||
|
log.Printf("HeaderClosing: %v\n", frame.ClosingStreamID)
|
||||||
closing.Close()
|
closing.Close()
|
||||||
}
|
}
|
||||||
stream.newFrameCh <- frame
|
stream.newFrameCh <- frame
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,35 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/cbeuw/ecies"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
|
ecdh "github.com/cbeuw/go-ecdh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// input ticket, return SID
|
// input ticket, return SID
|
||||||
func decryptSessionTicket(pv *ecies.PrivateKey, ticket []byte) ([]byte, error) {
|
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, error) {
|
||||||
ciphertext := make([]byte, 153)
|
ec := ecdh.NewCurve25519ECDH()
|
||||||
ciphertext[0] = 0x04
|
ephPub, _ := ec.Unmarshal(ticket[0:32])
|
||||||
copy(ciphertext[1:], ticket)
|
key, err := ec.GenerateSharedSecret(staticPv, ephPub)
|
||||||
plaintext, err := pv.Decrypt(ciphertext, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return plaintext[0:32], nil
|
SID := util.AESDecrypt(ticket[0:16], key, ticket[32:64])
|
||||||
|
return SID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRandom(random []byte, SID []byte, time int64) bool {
|
func validateRandom(random []byte, SID []byte, time int64) bool {
|
||||||
t := make([]byte, 8)
|
t := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(t, uint64(time/12*60*60))
|
binary.BigEndian.PutUint64(t, uint64(time/(12*60*60)))
|
||||||
rand := random[0:16]
|
rdm := random[0:16]
|
||||||
preHash := make([]byte, 56)
|
preHash := make([]byte, 56)
|
||||||
copy(preHash[0:32], SID)
|
copy(preHash[0:32], SID)
|
||||||
copy(preHash[32:40], t)
|
copy(preHash[32:40], t)
|
||||||
copy(preHash[40:56], rand)
|
copy(preHash[40:56], rdm)
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
|
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
|
||||||
|
|
@ -42,10 +45,12 @@ func TouchStone(ch *ClientHello, sta *State) (bool, []byte) {
|
||||||
}
|
}
|
||||||
sta.putUsedRandom(random)
|
sta.putUsedRandom(random)
|
||||||
|
|
||||||
SID, err := decryptSessionTicket(sta.pv, ch.extensions[[2]byte{0x00, 0x23}])
|
SID, err := decryptSessionTicket(sta.staticPv, ch.extensions[[2]byte{0x00, 0x23}])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("ts: %v\n", err)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
log.Printf("SID: %x\n", SID)
|
||||||
isSS := validateRandom(ch.random, SID, sta.Now().Unix())
|
isSS := validateRandom(ch.random, SID, sta.Now().Unix())
|
||||||
if !isSS {
|
if !isSS {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||||
"github.com/cbeuw/ecies"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
|
|
@ -26,18 +24,32 @@ type stateManager interface {
|
||||||
|
|
||||||
// State type stores the global state of the program
|
// State type stores the global state of the program
|
||||||
type State struct {
|
type State struct {
|
||||||
WebServerAddr string
|
|
||||||
Now func() time.Time
|
|
||||||
SS_LOCAL_HOST string
|
SS_LOCAL_HOST string
|
||||||
SS_LOCAL_PORT string
|
SS_LOCAL_PORT string
|
||||||
SS_REMOTE_HOST string
|
SS_REMOTE_HOST string
|
||||||
SS_REMOTE_PORT string
|
SS_REMOTE_PORT string
|
||||||
UsedRandomM sync.RWMutex
|
|
||||||
UsedRandom map[[32]byte]int
|
|
||||||
pv *ecies.PrivateKey
|
|
||||||
|
|
||||||
SessionsM sync.RWMutex
|
Now func() time.Time
|
||||||
Sessions map[[32]byte]*mux.Session
|
staticPv crypto.PrivateKey
|
||||||
|
usedRandomM sync.RWMutex
|
||||||
|
usedRandom map[[32]byte]int
|
||||||
|
sessionsM sync.RWMutex
|
||||||
|
sessions map[[32]byte]*mux.Session
|
||||||
|
|
||||||
|
WebServerAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||||
|
ret := &State{
|
||||||
|
SS_LOCAL_HOST: localHost,
|
||||||
|
SS_LOCAL_PORT: localPort,
|
||||||
|
SS_REMOTE_HOST: remoteHost,
|
||||||
|
SS_REMOTE_PORT: remotePort,
|
||||||
|
Now: nowFunc,
|
||||||
|
}
|
||||||
|
ret.usedRandom = make(map[[32]byte]int)
|
||||||
|
ret.sessions = make(map[[32]byte]*mux.Session)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// semi-colon separated value.
|
// semi-colon separated value.
|
||||||
|
|
@ -65,28 +77,15 @@ func ssvToJson(ssv string) (ret []byte) {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structue: [D 32 bytes][marshalled public key]
|
// base64 encoded 32 byte private key
|
||||||
func parseKey(b64 string) (*ecies.PrivateKey, error) {
|
func parseKey(b64 string) (crypto.PrivateKey, error) {
|
||||||
b, err := base64.StdEncoding.DecodeString(b64)
|
b, err := base64.StdEncoding.DecodeString(b64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var pv [32]byte
|
||||||
d := b[0:32]
|
copy(pv[:], b)
|
||||||
marshalled := b[32:]
|
return &pv, nil
|
||||||
x, y := elliptic.Unmarshal(ecies.DefaultCurve, marshalled)
|
|
||||||
pub := ecies.PublicKey{
|
|
||||||
X: x,
|
|
||||||
Y: y,
|
|
||||||
Curve: ecies.DefaultCurve,
|
|
||||||
Params: ecies.ParamsFromCurve(ecies.DefaultCurve),
|
|
||||||
}
|
|
||||||
|
|
||||||
pv := &ecies.PrivateKey{
|
|
||||||
PublicKey: pub,
|
|
||||||
D: new(big.Int).SetBytes(d),
|
|
||||||
}
|
|
||||||
return pv, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
|
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
|
||||||
|
|
@ -109,14 +108,17 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
|
|
||||||
sta.WebServerAddr = preParse.WebServerAddr
|
sta.WebServerAddr = preParse.WebServerAddr
|
||||||
pv, err := parseKey(preParse.Key)
|
pv, err := parseKey(preParse.Key)
|
||||||
sta.pv = pv
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sta.staticPv = pv
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sta *State) GetSession(SID [32]byte) *mux.Session {
|
func (sta *State) GetSession(SID [32]byte) *mux.Session {
|
||||||
sta.SessionsM.Lock()
|
sta.sessionsM.Lock()
|
||||||
defer sta.SessionsM.Unlock()
|
defer sta.sessionsM.Unlock()
|
||||||
if sesh, ok := sta.Sessions[SID]; ok {
|
if sesh, ok := sta.sessions[SID]; ok {
|
||||||
return sesh
|
return sesh
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -124,23 +126,23 @@ func (sta *State) GetSession(SID [32]byte) *mux.Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sta *State) PutSession(SID [32]byte, sesh *mux.Session) {
|
func (sta *State) PutSession(SID [32]byte, sesh *mux.Session) {
|
||||||
sta.SessionsM.Lock()
|
sta.sessionsM.Lock()
|
||||||
sta.Sessions[SID] = sesh
|
sta.sessions[SID] = sesh
|
||||||
sta.SessionsM.Unlock()
|
sta.sessionsM.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sta *State) getUsedRandom(random [32]byte) int {
|
func (sta *State) getUsedRandom(random [32]byte) int {
|
||||||
sta.UsedRandomM.Lock()
|
sta.usedRandomM.Lock()
|
||||||
defer sta.UsedRandomM.Unlock()
|
defer sta.usedRandomM.Unlock()
|
||||||
return sta.UsedRandom[random]
|
return sta.usedRandom[random]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutUsedRandom adds a random field into map UsedRandom
|
// PutUsedRandom adds a random field into map usedRandom
|
||||||
func (sta *State) putUsedRandom(random [32]byte) {
|
func (sta *State) putUsedRandom(random [32]byte) {
|
||||||
sta.UsedRandomM.Lock()
|
sta.usedRandomM.Lock()
|
||||||
sta.UsedRandom[random] = int(sta.Now().Unix())
|
sta.usedRandom[random] = int(sta.Now().Unix())
|
||||||
sta.UsedRandomM.Unlock()
|
sta.usedRandomM.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsedRandomCleaner clears the cache of used random fields every 12 hours
|
// UsedRandomCleaner clears the cache of used random fields every 12 hours
|
||||||
|
|
@ -148,12 +150,12 @@ func (sta *State) UsedRandomCleaner() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(12 * time.Hour)
|
time.Sleep(12 * time.Hour)
|
||||||
now := int(sta.Now().Unix())
|
now := int(sta.Now().Unix())
|
||||||
sta.UsedRandomM.Lock()
|
sta.usedRandomM.Lock()
|
||||||
for key, t := range sta.UsedRandom {
|
for key, t := range sta.usedRandom {
|
||||||
if now-t > 12*3600 {
|
if now-t > 12*3600 {
|
||||||
delete(sta.UsedRandom, key)
|
delete(sta.usedRandom, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sta.UsedRandomM.Unlock()
|
sta.usedRandomM.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ package util
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||||
)
|
)
|
||||||
|
|
||||||
func encrypt(iv []byte, key []byte, plaintext []byte) []byte {
|
func AESEncrypt(iv []byte, key []byte, plaintext []byte) []byte {
|
||||||
block, _ := aes.NewCipher(key)
|
block, _ := aes.NewCipher(key)
|
||||||
ciphertext := make([]byte, len(plaintext))
|
ciphertext := make([]byte, len(plaintext))
|
||||||
stream := cipher.NewCTR(block, iv)
|
stream := cipher.NewCTR(block, iv)
|
||||||
|
|
@ -16,7 +18,7 @@ func encrypt(iv []byte, key []byte, plaintext []byte) []byte {
|
||||||
return ciphertext
|
return ciphertext
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(iv []byte, key []byte, ciphertext []byte) []byte {
|
func AESDecrypt(iv []byte, key []byte, ciphertext []byte) []byte {
|
||||||
ret := make([]byte, len(ciphertext))
|
ret := make([]byte, len(ciphertext))
|
||||||
copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed
|
copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed
|
||||||
block, _ := aes.NewCipher(key)
|
block, _ := aes.NewCipher(key)
|
||||||
|
|
@ -32,8 +34,9 @@ func MakeObfs(key []byte) func(*mux.Frame) []byte {
|
||||||
binary.BigEndian.PutUint32(header[4:8], f.Seq)
|
binary.BigEndian.PutUint32(header[4:8], f.Seq)
|
||||||
binary.BigEndian.PutUint32(header[8:12], f.ClosingStreamID)
|
binary.BigEndian.PutUint32(header[8:12], f.ClosingStreamID)
|
||||||
// header: [StreamID 4 bytes][Seq 4 bytes][ClosingStreamID 4 bytes]
|
// header: [StreamID 4 bytes][Seq 4 bytes][ClosingStreamID 4 bytes]
|
||||||
iv := CryptoRandBytes(16)
|
iv := make([]byte, 16)
|
||||||
cipherheader := encrypt(iv, key, header)
|
io.ReadFull(rand.Reader, iv)
|
||||||
|
cipherheader := AESEncrypt(iv, key, header)
|
||||||
obfsed := make([]byte, len(f.Payload)+12+16)
|
obfsed := make([]byte, len(f.Payload)+12+16)
|
||||||
copy(obfsed[0:16], iv)
|
copy(obfsed[0:16], iv)
|
||||||
copy(obfsed[16:28], cipherheader)
|
copy(obfsed[16:28], cipherheader)
|
||||||
|
|
@ -48,7 +51,7 @@ func MakeObfs(key []byte) func(*mux.Frame) []byte {
|
||||||
func MakeDeobfs(key []byte) func([]byte) *mux.Frame {
|
func MakeDeobfs(key []byte) func([]byte) *mux.Frame {
|
||||||
deobfs := func(in []byte) *mux.Frame {
|
deobfs := func(in []byte) *mux.Frame {
|
||||||
peeled := PeelRecordLayer(in)
|
peeled := PeelRecordLayer(in)
|
||||||
header := decrypt(peeled[0:16], key, peeled[16:28])
|
header := AESDecrypt(peeled[0:16], key, peeled[16:28])
|
||||||
streamID := binary.BigEndian.Uint32(header[0:4])
|
streamID := binary.BigEndian.Uint32(header[0:4])
|
||||||
seq := binary.BigEndian.Uint32(header[4:8])
|
seq := binary.BigEndian.Uint32(header[4:8])
|
||||||
closingStreamID := binary.BigEndian.Uint32(header[8:12])
|
closingStreamID := binary.BigEndian.Uint32(header[8:12])
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
|
||||||
prand "math/rand"
|
prand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -24,18 +22,6 @@ func BtoInt(b []byte) int {
|
||||||
return int(sum)
|
return int(sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CryptoRandBytes generates a byte slice filled with cryptographically secure random bytes
|
|
||||||
func CryptoRandBytes(length int) []byte {
|
|
||||||
byteMax := big.NewInt(int64(256))
|
|
||||||
ret := make([]byte, length)
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
randInt, _ := rand.Int(rand.Reader, byteMax)
|
|
||||||
randByte := byte(randInt.Int64())
|
|
||||||
ret[i] = randByte
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed
|
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed
|
||||||
func PsudoRandBytes(length int, seed int64) []byte {
|
func PsudoRandBytes(length int, seed int64) []byte {
|
||||||
prand.Seed(seed)
|
prand.Seed(seed)
|
||||||
|
|
@ -65,7 +51,7 @@ func ReadTillDrain(conn net.Conn, buffer []byte) (n int, err error) {
|
||||||
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||||
for left != 0 {
|
for left != 0 {
|
||||||
if readPtr > len(buffer) || readPtr+left > len(buffer) {
|
if readPtr > len(buffer) || readPtr+left > len(buffer) {
|
||||||
err = errors.New("Reading TLS message: actual size greater than header's specification")
|
err = errors.New("Reading TLS message: message size greater than buffer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
|
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
|
||||||
|
|
@ -82,7 +68,6 @@ func ReadTillDrain(conn net.Conn, buffer []byte) (n int, err error) {
|
||||||
conn.SetReadDeadline(time.Time{})
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
n = 5 + dataLength
|
n = 5 + dataLength
|
||||||
buffer = buffer[:n]
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue