From 206b5782aeb479f8b4103a179cda3dc47c1ea01c Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 29 Oct 2021 16:01:50 -0700 Subject: [PATCH] refactor to a more usable base for servers/serverConfig --- app/main.go | 4 +-- go.mod | 6 ++--- go.sum | 8 ++++++ server.go | 40 ++++++++++-------------------- serverConfig.go | 65 +++++++++++++++++++++++++++++++++---------------- servers.go | 16 +++++++----- 6 files changed, 80 insertions(+), 59 deletions(-) diff --git a/app/main.go b/app/main.go index 7d28600..45c814e 100644 --- a/app/main.go +++ b/app/main.go @@ -81,9 +81,9 @@ func main() { server := cwtchserver.NewServer(serverConfig) log.Infoln("starting cwtch server...") - log.Infof("Server 'hash name': %s\n", server.HashName()) + log.Infof("Server %s\n", server.Onion()) - log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.Server())) + log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.ServerBundle())) if *flagExportTofu { // Todo: change all to server export diff --git a/go.mod b/go.mod index 8ff9ecf..52cc76b 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.14 require ( cwtch.im/cwtch v0.8.5 - git.openprivacy.ca/cwtch.im/tapir v0.4.4 - git.openprivacy.ca/openprivacy/connectivity v1.4.5 - git.openprivacy.ca/openprivacy/log v1.0.2 + git.openprivacy.ca/cwtch.im/tapir v0.4.9 + git.openprivacy.ca/openprivacy/connectivity v1.5.0 + git.openprivacy.ca/openprivacy/log v1.0.3 github.com/gtank/ristretto255 v0.1.2 github.com/mattn/go-sqlite3 v1.14.7 github.com/struCoder/pidusage v0.2.1 diff --git a/go.sum b/go.sum index d70cc96..de82a84 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cwtch.im/cwtch v0.8.0 h1:QDRaDBTXefFRPZPqUMtxoNhOcgXv0rl0bGjysSOmJX0= cwtch.im/cwtch v0.8.0/go.mod h1:+SY/4ueF1U7mK+CX8hZFbtd+GC1lx/cReo110KgtQAw= cwtch.im/cwtch v0.8.5 h1:W67jAF2oRwqWytbZEv1UeCqW0cU2x69tgUw8iy27xFA= cwtch.im/cwtch v0.8.5/go.mod h1:5GHxaaeVnKeXSU64IvtCKzkqhU8DRiLoVM+tiBT8kkc= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.openprivacy.ca/cwtch.im/tapir v0.4.0 h1:clG8uORt0NKEhT4P+Dpw1pzyUuYzYBMevGqn2pciKk8= git.openprivacy.ca/cwtch.im/tapir v0.4.0/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.1 h1:9LMpQX41IzecNNlRc1FZKXHg6wlFss679tFsa3vzb3Y= @@ -12,15 +14,21 @@ git.openprivacy.ca/cwtch.im/tapir v0.4.2 h1:bxMWZnVJXX4dqqOFS7ELW4iFkVL4GS8wiRkj git.openprivacy.ca/cwtch.im/tapir v0.4.2/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.4 h1:KyuTVmr9GYptTCeR7JDODjmhBBbnIBf9V3NSC4+6bHc= git.openprivacy.ca/cwtch.im/tapir v0.4.4/go.mod h1:qMFTdmDZITc1BLP1jSW0gVpLmvpg+Zjsh5ek8StwbFE= +git.openprivacy.ca/cwtch.im/tapir v0.4.9 h1:LXonlztwvI1F1++0IyomIcDH1/Bxzo+oN8YjGonNvjM= +git.openprivacy.ca/cwtch.im/tapir v0.4.9/go.mod h1:p4bHo3DAO8wwimU6JAeZXbfPQ4jnoA2bV+4YvknWTNQ= git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.4.3 h1:i2Ad/U9FlL9dKr2bhRck7lJ8NoWyGtoEfUwoCyMT0fU= git.openprivacy.ca/openprivacy/connectivity v1.4.3/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo= git.openprivacy.ca/openprivacy/connectivity v1.4.5 h1:UYMdCWPzEAP7LbqdMXGNXmfKjWlvfnKdmewBtnbgQRI= git.openprivacy.ca/openprivacy/connectivity v1.4.5/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ= +git.openprivacy.ca/openprivacy/connectivity v1.5.0 h1:ZxsR/ZaVKXIkD2x6FlajZn62ciNQjamrI4i/5xIpdoQ= +git.openprivacy.ca/openprivacy/connectivity v1.5.0/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw= git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= +git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0= +git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/server.go b/server.go index bd789e5..1239b9c 100644 --- a/server.go +++ b/server.go @@ -4,7 +4,6 @@ import ( "crypto/ed25519" "cwtch.im/cwtch/model" "encoding/base64" - "encoding/binary" "errors" "fmt" "git.openprivacy.ca/cwtch.im/server/metrics" @@ -20,7 +19,6 @@ import ( "git.openprivacy.ca/openprivacy/log" "os" "path" - "strings" "sync" ) @@ -39,9 +37,8 @@ type Server interface { GetStatistics() Statistics Delete(password string) error Onion() string - Server() string + ServerBundle() string TofuBundle() string - HashName() string GetAttribute(string) string SetAttribute(string, string) } @@ -152,13 +149,15 @@ func (s *server) Shutdown() { log.Infof("Shutting down server") s.lock.Lock() defer s.lock.Unlock() - s.service.Shutdown() - s.tokenTapirService.Shutdown() - log.Infof("Closing Token server Database...") - s.tokenServer.Close() - s.metricsPack.Stop() - s.running = true - s.SetAttribute(AttrEnabled, "false") + if s.running { + s.service.Shutdown() + s.tokenTapirService.Shutdown() + log.Infof("Closing Token server Database...") + s.tokenServer.Close() + s.metricsPack.Stop() + s.running = false + s.SetAttribute(AttrEnabled, "false") + } } // Statistics is an encapsulation of information about the server that an operator might want to know at a glance. @@ -194,11 +193,13 @@ func (s *server) Onion() string { return s.config.Onion() } -func (s *server) Server() string { +// ServerBundle returns a bundle of the server keys required to access it (torv3 keys are addresses) +func (s *server) ServerBundle() string { bundle := s.KeyBundle().Serialize() return fmt.Sprintf("server:%s", base64.StdEncoding.EncodeToString(bundle)) } +// TofuBundle returns a Server Bundle + a newly created group invite func (s *server) TofuBundle() string { group, _ := model.NewGroup(tor.GetTorV3Hostname(s.config.PublicKey)) invite, err := group.Invite() @@ -209,21 +210,6 @@ func (s *server) TofuBundle() string { return fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite) } -// TODO demo implementation only, not nearly enough entropy -// TODO Apache license -// https://github.com/dustinkirkland/petname/blob/master/usr/share/petname/small/names.txt -var namesSmall = []string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "haddock", "hagfish", "halibut", "hamster", "herring", "jackass", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "woodcock"} - -func (s *server) HashName() string { - var bytes []byte = s.config.PublicKey - var words []string - for i := 0; i < 8; i++ { - index := int(binary.BigEndian.Uint32(bytes[i*4:(i+1)*4])) % len(namesSmall) - words = append(words, namesSmall[index]) - } - return strings.Join(words, "-") -} - // GetAttribute gets a server attribute func (s *server) GetAttribute(key string) string { return s.config.GetAttribute(key) diff --git a/serverConfig.go b/serverConfig.go index 3f4f5cd..a7087d3 100644 --- a/serverConfig.go +++ b/serverConfig.go @@ -27,6 +27,20 @@ const ( // AttrEnabled is the attribute key for user toggle of server being enabled AttrEnabled = "enabled" + + // AttrStorageType is used by clients that may need info about stored server config types/styles + AttrStorageType = "storageType" +) + +const ( + // StorageTypeDefaultPassword is a AttrStorageType that indicated a app default password was used + StorageTypeDefaultPassword = "storage-default-password" + + // StorageTypePassword is a AttrStorageType that indicated a user password was used to protect the profile + StorageTypePassword = "storage-password" + + // StoreageTypeNoPassword is a AttrStorageType that indicated a no password was used to protect the profile + StoreageTypeNoPassword = "storage-no-password" ) // Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report @@ -54,8 +68,10 @@ type Config struct { ServerReporting Reporting `json:"serverReporting"` - attributes map[string]string - lock sync.Mutex + Attributes map[string]string `json:"attributes"` + + lock sync.Mutex + encFileStore v1.FileStore } // Identity returns an encapsulation of the servers keys @@ -69,7 +85,7 @@ func (config *Config) TokenServiceIdentity() primitives.Identity { } func initDefaultConfig(configDir, filename string, encrypted bool) *Config { - config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename} + config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename, Attributes: make(map[string]string)} id, pk := primitives.InitializeEphemeralIdentity() tid, tpk := primitives.InitializeEphemeralIdentity() @@ -83,8 +99,8 @@ func initDefaultConfig(configDir, filename string, encrypted bool) *Config { ReportingGroupID: "", ReportingServerAddr: "", } - config.attributes[AttrAutostart] = "false" - config.attributes[AttrEnabled] = "true" + config.Attributes[AttrAutostart] = "false" + config.Attributes[AttrEnabled] = "true" k := new(ristretto255.Scalar) b := make([]byte, 64) @@ -110,6 +126,7 @@ func LoadCreateDefaultConfigFile(configDir, filename string, encrypted bool, pas // CreateConfig creates a default config and saves it to a json file specified by filename // if the encrypted flag is true the config is store encrypted by password func CreateConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { + log.Debugf("CreateConfig for server with configDir: %s\n", configDir) os.Mkdir(configDir, 0700) config := initDefaultConfig(configDir, filename, encrypted) if encrypted { @@ -119,6 +136,7 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( return nil, err } config.key = key + config.encFileStore = v1.NewFileStore(configDir, ServerConfigFile, key) } config.Save() @@ -127,23 +145,23 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( // LoadConfig loads a Config from a json file specified by filename func LoadConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { - log.Infof("Loading config from %s\n", path.Join(configDir, filename)) - config := initDefaultConfig(configDir, filename, encrypted) - - raw, err := ioutil.ReadFile(path.Join(configDir, filename)) - if err != nil { - return nil, err - } - + var raw []byte + var err error if encrypted { salt, err := ioutil.ReadFile(path.Join(configDir, SaltFile)) if err != nil { return nil, err } key := v1.CreateKey(password, salt) - settingsStore := v1.NewFileStore(configDir, ServerConfigFile, key) - raw, err = settingsStore.Read() + config.encFileStore = v1.NewFileStore(configDir, ServerConfigFile, key) + raw, err = config.encFileStore.Read() + if err != nil { + log.Errorf("read enc bytes failed: %s\n", err) + return nil, err + } + } else { + raw, err = ioutil.ReadFile(path.Join(configDir, filename)) if err != nil { return nil, err } @@ -161,17 +179,20 @@ func LoadConfig(configDir, filename string, encrypted bool, password string) (*C // Save dumps the latest version of the config to a json file given by filename func (config *Config) Save() error { - log.Infof("Saving config to %s\n", path.Join(config.ConfigDir, config.FilePath)) + config.lock.Lock() + defer config.lock.Unlock() bytes, _ := json.MarshalIndent(config, "", "\t") if config.Encrypted { - settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) - return settingStore.Write(bytes) + //settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) + return config.encFileStore.Write(bytes) } return ioutil.WriteFile(path.Join(config.ConfigDir, config.FilePath), bytes, 0600) } // CheckPassword returns true if the given password produces the same key as the current stored key, otherwise false. func (config *Config) CheckPassword(checkpass string) bool { + config.lock.Lock() + defer config.lock.Unlock() salt, err := ioutil.ReadFile(path.Join(config.ConfigDir, SaltFile)) if err != nil { return false @@ -182,14 +203,16 @@ func (config *Config) CheckPassword(checkpass string) bool { // Onion returns the .onion url for the server func (config *Config) Onion() string { + config.lock.Lock() + defer config.lock.Unlock() return tor.GetTorV3Hostname(config.PublicKey) + ".onion" } // SetAttribute sets a server attribute func (config *Config) SetAttribute(key, val string) { config.lock.Lock() - defer config.lock.Unlock() - config.attributes[key] = val + config.Attributes[key] = val + config.lock.Unlock() config.Save() } @@ -197,5 +220,5 @@ func (config *Config) SetAttribute(key, val string) { func (config *Config) GetAttribute(key string) string { config.lock.Lock() defer config.lock.Unlock() - return config.attributes[key] + return config.Attributes[key] } diff --git a/servers.go b/servers.go index f8e4f74..bf619e4 100644 --- a/servers.go +++ b/servers.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "git.openprivacy.ca/openprivacy/connectivity" + "git.openprivacy.ca/openprivacy/log" "io/ioutil" "path" "sync" @@ -37,7 +38,7 @@ type servers struct { // NewServers returns a Servers interface to manage a collection of servers // expecting directory: $CWTCH_HOME/servers func NewServers(acn connectivity.ACN, directory string) Servers { - return &servers{acn: acn, directory: directory} + return &servers{acn: acn, directory: directory, servers: make(map[string]Server)} } // LoadServers will attempt to load any servers in the servers directory that are encrypted with the supplied password @@ -50,16 +51,19 @@ func (s *servers) LoadServers(password string) ([]string, error) { if err != nil { return nil, fmt.Errorf("error: cannot read server directory: %v", err) } - loadedServers := []string{} for _, dir := range dirs { newConfig, err := LoadConfig(path.Join(s.directory, dir.Name()), ServerConfigFile, true, password) - if _, exists := s.servers[newConfig.Onion()]; err == nil && !exists { - server := NewServer(newConfig) - s.servers[server.Onion()] = server - loadedServers = append(loadedServers, server.Onion()) + if err == nil { + if _, exists := s.servers[newConfig.Onion()]; !exists { + log.Debugf("Loaded config, building server for %s\n", newConfig.Onion()) + server := NewServer(newConfig) + s.servers[server.Onion()] = server + loadedServers = append(loadedServers, server.Onion()) + } } } + log.Infof("LoadServers returning: %s\n", loadedServers) return loadedServers, nil }