| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- package tapdance
- import (
- "crypto/x509"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "io/ioutil"
- "net"
- "os"
- "path"
- "strings"
- "sync"
- "github.com/golang/protobuf/proto"
- pb "github.com/refraction-networking/gotapdance/protobuf"
- ps "github.com/refraction-networking/gotapdance/tapdance/phantoms"
- )
- type assets struct {
- sync.RWMutex
- path string
- config *pb.ClientConf
- roots *x509.CertPool
- filenameRoots string
- filenameClientConf string
- socksAddr string
- }
- // could reset this internally to refresh assets and avoid woes of singleton testing
- var assetsInstance *assets
- var assetsOnce sync.Once
- // Assets is an access point to asset managing singleton.
- // First access to singleton sets path. Assets(), if called
- // before SetAssetsDir() sets path to "./assets/"
- func Assets() *assets {
- var err error
- _initAssets := func() { err = initAssets("./assets/") }
- assetsOnce.Do(_initAssets)
- if err != nil {
- Logger().Warnf("error getting assets: %v", err)
- }
- return assetsInstance
- }
- // AssetsSetDir sets the directory to read assets from.
- // Functionally equivalent to Assets() after initialization, unless dir changes.
- func AssetsSetDir(dir string) (*assets, error) {
- var err error
- _initAssets := func() { err = initAssets(dir) }
- if assetsInstance != nil {
- assetsInstance.Lock()
- defer assetsInstance.Unlock()
- if dir != assetsInstance.path {
- if _, err := os.Stat(dir); err != nil {
- Logger().Warnf("Assets path unchanged %v.\n", err)
- return assetsInstance, err
- }
- Logger().Warnf("Assets path changed %s->%s. (Re)initializing", assetsInstance.path, dir)
- assetsInstance.path = dir
- err = assetsInstance.readConfigs()
- return assetsInstance, err
- }
- }
- assetsOnce.Do(_initAssets)
- return assetsInstance, err
- }
- func getDefaultKey() []byte {
- keyStr := "a1cb97be697c5ed5aefd78ffa4db7e68101024603511e40a89951bc158807177"
- key := make([]byte, hex.DecodedLen(len(keyStr)))
- hex.Decode(key, []byte(keyStr))
- return key
- }
- func getDefaultTapdanceKey() []byte {
- keyStr := "515868be7f45ab6f310afed4b229b7a479fc9fde553dea4ccdb369ab1899e70c"
- key := make([]byte, hex.DecodedLen(len(keyStr)))
- hex.Decode(key, []byte(keyStr))
- return key
- }
- func initAssets(path string) error {
- var defaultDecoys = []*pb.TLSDecoySpec{
- pb.InitTLSDecoySpec("192.122.190.104", "tapdance1.freeaeskey.xyz"),
- pb.InitTLSDecoySpec("192.122.190.105", "tapdance2.freeaeskey.xyz"),
- pb.InitTLSDecoySpec("192.122.190.106", "tapdance3.freeaeskey.xyz"),
- }
- defaultKey := getDefaultTapdanceKey()
- defaultConjureKey := getDefaultKey()
- defualtKeyType := pb.KeyType_AES_GCM_128
- defaultPubKey := pb.PubKey{Key: defaultKey, Type: &defualtKeyType}
- defaultConjurePubKey := pb.PubKey{Key: defaultConjureKey, Type: &defualtKeyType}
- defaultGeneration := uint32(1)
- defaultDecoyList := pb.DecoyList{TlsDecoys: defaultDecoys}
- defaultClientConf := pb.ClientConf{
- DecoyList: &defaultDecoyList,
- DefaultPubkey: &defaultPubKey,
- ConjurePubkey: &defaultConjurePubKey,
- Generation: &defaultGeneration,
- }
- assetsInstance = &assets{
- path: path,
- config: &defaultClientConf,
- filenameRoots: "roots",
- filenameClientConf: "ClientConf",
- socksAddr: "",
- }
- err := assetsInstance.readConfigs()
- return err
- }
- func (a *assets) GetAssetsDir() string {
- a.RLock()
- defer a.RUnlock()
- return a.path
- }
- func (a *assets) readConfigs() error {
- readRoots := func(filename string) error {
- rootCerts, err := ioutil.ReadFile(filename)
- if err != nil {
- return err
- }
- roots := x509.NewCertPool()
- ok := roots.AppendCertsFromPEM(rootCerts)
- if !ok {
- return errors.New("Failed to parse root certificates")
- }
- a.roots = roots
- return nil
- }
- readClientConf := func(filename string) error {
- buf, err := ioutil.ReadFile(filename)
- if err != nil {
- return err
- }
- clientConf := &pb.ClientConf{}
- err = proto.Unmarshal(buf, clientConf)
- if err != nil {
- return err
- }
- a.config = clientConf
- return nil
- }
- var err error
- Logger().Infoln("Assets: reading from folder " + a.path)
- rootsFilename := path.Join(a.path, a.filenameRoots)
- err = readRoots(rootsFilename)
- if err != nil {
- Logger().Warn("Assets: failed to read root ca file: " + err.Error())
- } else {
- Logger().Infoln("X.509 root CAs successfully read from " + rootsFilename)
- }
- // Parse ClientConf for Decoys and Phantoms List
- clientConfFilename := path.Join(a.path, a.filenameClientConf)
- err = readClientConf(clientConfFilename)
- if err != nil {
- Logger().Warn("Assets: failed to read ClientConf file: " + err.Error())
- } else {
- Logger().Infoln("Client config successfully read from " + clientConfFilename)
- }
- return err
- }
- // Picks random decoy, returns Server Name Indication and addr in format ipv4:port
- func (a *assets) GetDecoyAddress() (sni string, addr string) {
- a.RLock()
- defer a.RUnlock()
- decoys := a.config.GetDecoyList().GetTlsDecoys()
- if len(decoys) == 0 {
- return "", ""
- }
- decoyIndex := getRandInt(0, len(decoys)-1)
- ip := make(net.IP, 4)
- binary.BigEndian.PutUint32(ip, decoys[decoyIndex].GetIpv4Addr())
- //[TODO]{priority:winter-break}: what checks need to be done, and what's guaranteed?
- addr = ip.To4().String() + ":443"
- sni = decoys[decoyIndex].GetHostname()
- return
- }
- // Get all Decoys from ClientConf
- func (a *assets) GetAllDecoys() []*pb.TLSDecoySpec {
- return a.config.GetDecoyList().GetTlsDecoys()
- }
- // Get all Decoys from ClientConf that have an IPv6 address
- func (a *assets) GetV6Decoys() []*pb.TLSDecoySpec {
- v6Decoys := make([]*pb.TLSDecoySpec, 0)
- allDecoys := a.config.GetDecoyList().GetTlsDecoys()
- for _, decoy := range allDecoys {
- if decoy.GetIpv6Addr() != nil {
- v6Decoys = append(v6Decoys, decoy)
- }
- }
- return v6Decoys
- }
- // Get all Decoys from ClientConf that have an IPv6 address
- func (a *assets) GetV4Decoys() []*pb.TLSDecoySpec {
- v6Decoys := make([]*pb.TLSDecoySpec, 0)
- allDecoys := a.config.GetDecoyList().GetTlsDecoys()
- for _, decoy := range allDecoys {
- if decoy.GetIpv4Addr() != 0 {
- v6Decoys = append(v6Decoys, decoy)
- }
- }
- return v6Decoys
- }
- // GetDecoy - Gets random DecoySpec
- func (a *assets) GetDecoy() *pb.TLSDecoySpec {
- a.RLock()
- defer a.RUnlock()
- decoys := a.config.GetDecoyList().GetTlsDecoys()
- chosenDecoy := &pb.TLSDecoySpec{}
- if len(decoys) == 0 {
- return chosenDecoy
- }
- decoyIndex := getRandInt(0, len(decoys)-1)
- chosenDecoy = decoys[decoyIndex]
- //[TODO]{priority:soon} stop enforcing values >= defaults.
- // Fix ackhole instead
- // No value checks when using
- if chosenDecoy.GetTimeout() < timeoutMin {
- timeout := uint32(timeoutMax)
- chosenDecoy.Timeout = &timeout
- }
- if chosenDecoy.GetTcpwin() < sendLimitMin {
- tcpWin := uint32(sendLimitMax)
- chosenDecoy.Tcpwin = &tcpWin
- }
- return chosenDecoy
- }
- // GetDecoy - Gets random IPv6 DecoySpec
- func (a *assets) GetV6Decoy() *pb.TLSDecoySpec {
- a.RLock()
- defer a.RUnlock()
- decoys := a.GetV6Decoys()
- chosenDecoy := &pb.TLSDecoySpec{}
- if len(decoys) == 0 {
- return chosenDecoy
- }
- decoyIndex := getRandInt(0, len(decoys)-1)
- chosenDecoy = decoys[decoyIndex]
- // No enforcing TCPWIN etc. values because this is conjure only
- return chosenDecoy
- }
- func (a *assets) GetRoots() *x509.CertPool {
- a.RLock()
- defer a.RUnlock()
- return a.roots
- }
- func (a *assets) GetPubkey() *[32]byte {
- a.RLock()
- defer a.RUnlock()
- var pKey [32]byte
- copy(pKey[:], a.config.GetDefaultPubkey().GetKey()[:])
- return &pKey
- }
- func (a *assets) GetConjurePubkey() *[32]byte {
- a.RLock()
- defer a.RUnlock()
- var pKey [32]byte
- copy(pKey[:], a.config.GetConjurePubkey().GetKey()[:])
- return &pKey
- }
- func (a *assets) GetGeneration() uint32 {
- a.RLock()
- defer a.RUnlock()
- return a.config.GetGeneration()
- }
- // Set ClientConf generation and store config to disk
- func (a *assets) SetGeneration(gen uint32) (err error) {
- a.Lock()
- defer a.Unlock()
- copyGen := gen
- a.config.Generation = ©Gen
- err = a.saveClientConf()
- return
- }
- // Set Public key and store config to disk
- func (a *assets) SetPubkey(pubkey *pb.PubKey) (err error) {
- a.Lock()
- defer a.Unlock()
- a.config.DefaultPubkey = pubkey
- err = a.saveClientConf()
- return
- }
- // Set ClientConf and store config to disk - if an error occurs (parse error or
- // write to file error) the error will be logged and the update will be aborted.
- func (a *assets) SetClientConf(conf *pb.ClientConf) (err error) {
- a.Lock()
- defer a.Unlock()
- origConf := a.config
- a.config = conf
- err = a.saveClientConf()
- if err != nil {
- a.config = origConf
- }
- return
- }
- // Not goroutine-safe, use at your own risk
- func (a *assets) GetClientConfPtr() *pb.ClientConf {
- return a.config
- }
- // Overwrite currently used decoys and store config to disk
- func (a *assets) SetDecoys(decoys []*pb.TLSDecoySpec) (err error) {
- a.Lock()
- defer a.Unlock()
- if a.config.DecoyList == nil {
- a.config.DecoyList = &pb.DecoyList{}
- }
- a.config.DecoyList.TlsDecoys = decoys
- err = a.saveClientConf()
- return
- }
- // Checks if decoy is in currently used ClientConf decoys list
- func (a *assets) IsDecoyInList(decoy *pb.TLSDecoySpec) bool {
- ipv4str := decoy.GetIpAddrStr()
- hostname := decoy.GetHostname()
- a.RLock()
- defer a.RUnlock()
- for _, d := range a.config.GetDecoyList().GetTlsDecoys() {
- if strings.Compare(d.GetHostname(), hostname) == 0 &&
- strings.Compare(d.GetIpAddrStr(), ipv4str) == 0 {
- return true
- }
- }
- return false
- }
- func (a *assets) saveClientConf() error {
- buf, err := proto.Marshal(a.config)
- if err != nil {
- return err
- }
- filename := path.Join(a.path, a.filenameClientConf)
- tmpFilename := path.Join(a.path, "."+a.filenameClientConf+"."+getRandString(5)+".tmp")
- err = ioutil.WriteFile(tmpFilename, buf[:], 0644)
- if err != nil {
- return err
- }
- return os.Rename(tmpFilename, filename)
- }
- // SetStatsSocksAddr - Provide a socks address for reporting stats from the client in the form "addr:port"
- func (a *assets) SetStatsSocksAddr(addr string) {
- a.socksAddr = addr
- }
- // GetPhantomSubnets -
- func (a *assets) GetPhantomSubnets() *pb.PhantomSubnetsList {
- a.RLock()
- defer a.RUnlock()
- if a.config == nil {
- return ps.GetDefaultPhantomSubnets()
- }
- if phantomSubnetsList := a.config.GetPhantomSubnetsList(); phantomSubnetsList != nil {
- return phantomSubnetsList
- }
- return ps.GetDefaultPhantomSubnets()
- }
- // SetPhantomSubnets -
- func (a *assets) SetPhantomSubnets(subnetConf *pb.PhantomSubnetsList) error {
- a.Lock()
- defer a.Unlock()
- if a.config == nil {
- a.config = &pb.ClientConf{}
- }
- a.config.PhantomSubnetsList = subnetConf
- err := a.saveClientConf()
- return err
- }
|