diff --git a/coniksbots/bot.go b/application/bots/bot.go similarity index 98% rename from coniksbots/bot.go rename to application/bots/bot.go index accd5e0..4621e1e 100644 --- a/coniksbots/bot.go +++ b/application/bots/bot.go @@ -3,7 +3,7 @@ // Currently, this interface is used to implement a Twitter account // verification bot. -package coniksbots +package bots import ( "bytes" diff --git a/application/bots/config.go b/application/bots/config.go new file mode 100644 index 0000000..a4d5f84 --- /dev/null +++ b/application/bots/config.go @@ -0,0 +1,52 @@ +package bots + +import ( + "github.com/coniks-sys/coniks-go/application" +) + +// A TwitterConfig contains the address of the named UNIX socket +// through which the bot and the CONIKS server communicate, +// the OAuth information needed to authenticate the bot with Twitter, +// and the bot's reserved Twitter handle. These values are specified +// in a configuration file, which is read at initialization time. +type TwitterConfig struct { + CONIKSAddress string `toml:"coniks_address"` + TwitterOAuth `toml:"twitter_oauth"` + Handle string `toml:"twitter_bot_handle"` +} + +// A TwitterOAuth contains the four secret values needed to authenticate +// the bot with Twitter. These values are unique to each application +// that uses the Twitter API to access an account's feed and direct +// messages, and must be generated via Twitter's developer portal. +type TwitterOAuth struct { + ConsumerKey string + ConsumerSecret string + AccessToken string + AccessSecret string +} + +var _ application.AppConfig = (*TwitterConfig)(nil) + +// NewTwitterConfig initializes a new Twitter registration bot configuration +// with the given server address, Twitter handle, and OAuth credentials. +func NewTwitterConfig(addr, handle string, oauth TwitterOAuth) *TwitterConfig { + var conf = TwitterConfig{ + CONIKSAddress: addr, + Handle: handle, + TwitterOAuth: oauth, + } + + return &conf +} + +// Load initializes a Twitter registration proxy configuration from the +// corresponding config file. +func (conf *TwitterConfig) Load(file string) error { + tmp, err := application.LoadConfig(file) + if err != nil { + return err + } + conf = tmp.(*TwitterConfig) + return nil +} diff --git a/coniksbots/doc.go b/application/bots/doc.go similarity index 75% rename from coniksbots/doc.go rename to application/bots/doc.go index f676ead..d405044 100644 --- a/coniksbots/doc.go +++ b/application/bots/doc.go @@ -1,5 +1,5 @@ /* -Package coniksbots implements the CONIKS account verification +Package bots implements the CONIKS account verification protocol for first-party identity providers. Many communication services provide user identifiers for @@ -9,11 +9,11 @@ wishing to communicate securely often opt to use a third-party end-to-end encrypted communication service, which allows them to connect their first-party account. -coniksbots provides such third-party secure communication services +bots provides such third-party secure communication services that use CONIKS for key management with a mechanism for ensuring that the first-party usernames registered with the CONIKS key directory belong to a legitimate first-party account. -More specifically, coniksbots provides registration proxies which +More specifically, bots provides registration proxies which verify that each first-party username belongs to a corresponding first-party account before forwarding the new registration to the third-party CONIKS server. @@ -28,10 +28,5 @@ Twitter Bot This module provides a registration proxy for Twitter accounts that implements the CONIKS account verification Bot interface. - -Cli - -This subpackage provides an executable reference implementation for a CONIKS -registration proxy for Twitter accounts. */ -package coniksbots +package bots diff --git a/coniksbots/twitterbot.go b/application/bots/twitterbot.go similarity index 85% rename from coniksbots/twitterbot.go rename to application/bots/twitterbot.go index 57e7287..7d433c4 100644 --- a/coniksbots/twitterbot.go +++ b/application/bots/twitterbot.go @@ -1,7 +1,7 @@ // A registration proxy for Twitter accounts that implements the // CONIKS account verification Bot interface. -package coniksbots +package bots import ( "fmt" @@ -10,8 +10,7 @@ import ( "strings" "time" - "github.com/BurntSushi/toml" - "github.com/coniks-sys/coniks-go/coniksserver" + "github.com/coniks-sys/coniks-go/application" "github.com/coniks-sys/coniks-go/protocol" "github.com/dghubble/go-twitter/twitter" "github.com/dghubble/oauth1" @@ -34,28 +33,6 @@ type TwitterBot struct { var _ Bot = (*TwitterBot)(nil) -// A TwitterConfig contains the address of the named UNIX socket -// through which the bot and the CONIKS server communicate, -// the OAuth information needed to authenticate the bot with Twitter, -// and the bot's reserved Twitter handle. These values are specified -// in a configuration file, which is read at initialization time. -type TwitterConfig struct { - CONIKSAddress string `toml:"coniks_address"` - TwitterOAuth `toml:"twitter_oauth"` - Handle string `toml:"twitter_bot_handle"` -} - -// A TwitterOAuth contains the four secret values needed to authenticate -// the bot with Twitter. These values are unique to each application -// that uses the Twitter API to access an account's feed and direct -// messages, and must be generated via Twitter's developer portal. -type TwitterOAuth struct { - ConsumerKey string - ConsumerSecret string - AccessToken string - AccessSecret string -} - // NewTwitterBot constructs a new account verification bot for Twitter // accounts that implements the Bot interface. // @@ -66,16 +43,15 @@ type TwitterOAuth struct { // tuple. Otherwise, it returns a TwitterBot struct // with the appropriate values obtained during the setup. func NewTwitterBot(path string) (Bot, error) { - var conf TwitterConfig - if _, err := toml.DecodeFile(path, &conf); err != nil { - return nil, fmt.Errorf("Failed to load config: %v", err) + var conf *TwitterConfig = &TwitterConfig{} + if err := conf.Load(path); err != nil { + return nil, err } // Notify if the CONIKS key server is down if _, err := os.Stat(conf.CONIKSAddress); os.IsNotExist(err) { return nil, fmt.Errorf("CONIKS Key Server is down") } - auth := conf.TwitterOAuth config := oauth1.NewConfig(auth.ConsumerKey, auth.ConsumerSecret) token := oauth1.NewToken(auth.AccessToken, auth.AccessSecret) @@ -167,7 +143,7 @@ func (bot *TwitterBot) Stop() { func (bot *TwitterBot) HandleRegistration(username string, msg []byte) string { // validate request message invalid := false - req, err := coniksserver.UnmarshalRequest(msg) + req, err := application.UnmarshalRequest(msg) if err != nil { invalid = true } else { @@ -180,7 +156,7 @@ func (bot *TwitterBot) HandleRegistration(username string, msg []byte) string { } if invalid { log.Println("[registration bot] Malformed client request") - res, err := coniksserver.MarshalResponse( + res, err := application.MarshalResponse( protocol.NewErrorResponse(protocol.ErrMalformedMessage)) if err != nil { panic(err) @@ -192,7 +168,7 @@ func (bot *TwitterBot) HandleRegistration(username string, msg []byte) string { res, err := SendRequestToCONIKS(bot.coniksAddress, msg) if err != nil { log.Println("[registration bot] " + err.Error()) - res, err := coniksserver.MarshalResponse( + res, err := application.MarshalResponse( protocol.NewErrorResponse(protocol.ErrDirectory)) if err != nil { panic(err) diff --git a/coniksbots/twitterbot_test.go b/application/bots/twitterbot_test.go similarity index 98% rename from coniksbots/twitterbot_test.go rename to application/bots/twitterbot_test.go index 584b28c..cb591b8 100644 --- a/coniksbots/twitterbot_test.go +++ b/application/bots/twitterbot_test.go @@ -1,4 +1,4 @@ -package coniksbots +package bots import ( "encoding/json" diff --git a/coniksclient/config.go b/application/client/config.go similarity index 50% rename from coniksclient/config.go rename to application/client/config.go index e4beebb..7049954 100644 --- a/coniksclient/config.go +++ b/application/client/config.go @@ -1,12 +1,8 @@ -package coniksclient +package client import ( - "fmt" - "io/ioutil" - - "github.com/BurntSushi/toml" + "github.com/coniks-sys/coniks-go/application" "github.com/coniks-sys/coniks-go/crypto/sign" - "github.com/coniks-sys/coniks-go/utils" ) // Config contains the client's configuration needed to send a request to a @@ -26,27 +22,36 @@ type Config struct { Address string `toml:"address"` } -// LoadConfig returns a client's configuration read from the given filename. +var _ application.AppConfig = (*Config)(nil) + +// NewConfig initializes a new client configuration with the given +// server signing public key path, registration address, and +// server address. +func NewConfig(signPubkeyPath, regAddr, serverAddr string) *Config { + var conf = Config{ + SignPubkeyPath: signPubkeyPath, + RegAddress: regAddr, + Address: serverAddr, + } + + return &conf +} + +// Load initializes a client's configuration from the given file. // It reads the signing public-key file and parses the actual key. -// If there is any parsing or IO-error it returns an error (and the returned -// config will be nil). -func LoadConfig(file string) (*Config, error) { - var conf Config - if _, err := toml.DecodeFile(file, &conf); err != nil { - return nil, fmt.Errorf("Failed to load config: %v", err) +func (conf *Config) Load(file string) error { + tmp, err := application.LoadConfig(file) + if err != nil { + return err } + conf = tmp.(*Config) // load signing key - signPath := utils.ResolvePath(conf.SignPubkeyPath, file) - signPubKey, err := ioutil.ReadFile(signPath) + signPubKey, err := application.LoadSigningPubKey(conf.SignPubkeyPath, file) if err != nil { - return nil, fmt.Errorf("Cannot read signing key: %v", err) + return err } - if len(signPubKey) != sign.PublicKeySize { - return nil, fmt.Errorf("Signing public-key must be 32 bytes (got %d)", len(signPubKey)) - } - conf.SigningPubKey = signPubKey - return &conf, nil + return nil } diff --git a/application/client/encoding.go b/application/client/encoding.go new file mode 100644 index 0000000..1d6fb58 --- /dev/null +++ b/application/client/encoding.go @@ -0,0 +1,25 @@ +package client + +import ( + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/protocol" +) + +// CreateRegistrationMsg returns a JSON encoding of +// a protocol.RegistrationRequest for the given (name, key) pair. +func CreateRegistrationMsg(name string, key []byte) ([]byte, error) { + return application.MarshalRequest(protocol.RegistrationType, + &protocol.RegistrationRequest{ + Username: name, + Key: key, + }) +} + +// CreateKeyLookupMsg returns a JSON encoding of +// a protocol.KeyLookupRequest for the given name. +func CreateKeyLookupMsg(name string) ([]byte, error) { + return application.MarshalRequest(protocol.KeyLookupType, + &protocol.RegistrationRequest{ + Username: name, + }) +} diff --git a/application/config.go b/application/config.go new file mode 100644 index 0000000..d5f9345 --- /dev/null +++ b/application/config.go @@ -0,0 +1,61 @@ +package application + +import ( + "bytes" + "fmt" + "io/ioutil" + + "github.com/BurntSushi/toml" + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/utils" +) + +// AppConfig is the generic type used to specify the configuration of +// any kind of CONIKS application-level executable (e.g. key server, +// client etc.). +type AppConfig interface { + Load(file string) error +} + +// LoadSigningPubKey loads a public signing key at the given path +// specified in the given config file. +// If there is any parsing error or the key is malformed, +// LoadSigningPubKey() returns an error with a nil key. +func LoadSigningPubKey(path, file string) (sign.PublicKey, error) { + signPath := utils.ResolvePath(path, file) + signPubKey, err := ioutil.ReadFile(signPath) + if err != nil { + return nil, fmt.Errorf("Cannot read signing key: %v", err) + } + if len(signPubKey) != sign.PublicKeySize { + return nil, fmt.Errorf("Signing public-key must be 32 bytes (got %d)", len(signPubKey)) + } + return signPubKey, nil +} + +// LoadConfig loads an application configuration from the given toml-encoded +// file. If there is any decoding error, an LoadConfig() returns an error +// with a nil config. +func LoadConfig(file string) (AppConfig, error) { + var conf AppConfig + if _, err := toml.DecodeFile(file, &conf); err != nil { + return nil, fmt.Errorf("Failed to load config: %v", err) + } + return conf, nil +} + +// SaveConfig stores the given configuration conf in the given +// file using toml encoding. +// If there is any encoding or IO error, SaveConfig() returns an error. +func SaveConfig(file string, conf AppConfig) error { + var confBuf bytes.Buffer + + e := toml.NewEncoder(&confBuf) + if err := e.Encode(conf); err != nil { + return err + } + if err := utils.WriteFile(file, confBuf.Bytes(), 0644); err != nil { + return err + } + return nil +} diff --git a/application/encoding.go b/application/encoding.go new file mode 100644 index 0000000..467b17c --- /dev/null +++ b/application/encoding.go @@ -0,0 +1,106 @@ +// Defines methods/functions to encode/decode messages between client +// and server. Currently this module supports JSON marshal/unmarshal only. +// Protobuf will be supported in the future. + +package application + +import ( + "encoding/json" + + "github.com/coniks-sys/coniks-go/protocol" +) + +// MarshalRequest returns a JSON encoding of the client's request. +func MarshalRequest(reqType int, request interface{}) ([]byte, error) { + return json.Marshal(&protocol.Request{ + Type: reqType, + Request: request, + }) +} + +// UnmarshalRequest parses a JSON-encoded request msg and +// creates the corresponding protocol.Request, which will be handled +// by the server. +func UnmarshalRequest(msg []byte) (*protocol.Request, error) { + var content json.RawMessage + req := protocol.Request{ + Request: &content, + } + if err := json.Unmarshal(msg, &req); err != nil { + return nil, err + } + var request interface{} + switch req.Type { + case protocol.RegistrationType: + request = new(protocol.RegistrationRequest) + case protocol.KeyLookupType: + request = new(protocol.KeyLookupRequest) + case protocol.KeyLookupInEpochType: + request = new(protocol.KeyLookupInEpochRequest) + case protocol.MonitoringType: + request = new(protocol.MonitoringRequest) + } + if err := json.Unmarshal(content, &request); err != nil { + return nil, err + } + req.Request = request + return &req, nil +} + +// MarshalResponse returns a JSON encoding of the server's response. +func MarshalResponse(response *protocol.Response) ([]byte, error) { + return json.Marshal(response) +} + +// UnmarshalResponse decodes the given message into a protocol.Response +// according to the given request type t. The request types are integer +// constants defined in the protocol package. +func UnmarshalResponse(t int, msg []byte) *protocol.Response { + type Response struct { + Error protocol.ErrorCode + DirectoryResponse json.RawMessage + } + var res Response + if err := json.Unmarshal(msg, &res); err != nil { + return &protocol.Response{ + Error: protocol.ErrMalformedMessage, + } + } + + // DirectoryResponse is omitempty for the places + // where Error is in Errors + if res.DirectoryResponse == nil { + if !protocol.Errors[res.Error] { + return &protocol.Response{ + Error: protocol.ErrMalformedMessage, + } + } + return &protocol.Response{ + Error: res.Error, + } + } + + switch t { + case protocol.RegistrationType, protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: + response := new(protocol.DirectoryProof) + if err := json.Unmarshal(res.DirectoryResponse, &response); err != nil { + return &protocol.Response{ + Error: protocol.ErrMalformedMessage, + } + } + return &protocol.Response{ + Error: res.Error, + DirectoryResponse: response, + } + default: + panic("Unknown request type") + } +} + +func malformedClientMsg(err error) *protocol.Response { + // check if we're just propagating a message + if err == nil { + err = protocol.ErrMalformedMessage + } + return protocol.NewErrorResponse(protocol.ErrMalformedMessage) +} diff --git a/coniksclient/encoding_test.go b/application/encoding_test.go similarity index 92% rename from coniksclient/encoding_test.go rename to application/encoding_test.go index c3f524c..87433db 100644 --- a/coniksclient/encoding_test.go +++ b/application/encoding_test.go @@ -1,11 +1,10 @@ -package coniksclient +package application import ( "bytes" "encoding/json" "testing" - "github.com/coniks-sys/coniks-go/coniksserver" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/directory" ) @@ -41,7 +40,7 @@ func TestUnmarshalSampleMessage(t *testing.T) { res := d.Register(&protocol.RegistrationRequest{ Username: "alice", Key: []byte("key")}) - msg, _ := coniksserver.MarshalResponse(res) + msg, _ := MarshalResponse(res) response := UnmarshalResponse(protocol.RegistrationType, []byte(msg)) str := response.DirectoryResponse.(*protocol.DirectoryProof).STR[0] if !bytes.Equal(d.LatestSTR().Serialize(), str.Serialize()) { diff --git a/utils/logger.go b/application/logger.go similarity index 99% rename from utils/logger.go rename to application/logger.go index d9529d9..5e5ed0d 100644 --- a/utils/logger.go +++ b/application/logger.go @@ -1,4 +1,4 @@ -package utils +package application import ( "strings" diff --git a/application/server/config.go b/application/server/config.go new file mode 100644 index 0000000..297e32d --- /dev/null +++ b/application/server/config.go @@ -0,0 +1,89 @@ +package server + +import ( + "fmt" + "io/ioutil" + + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/utils" +) + +// A Config contains configuration values +// which are read at initialization time from +// a TOML format configuration file. +type Config struct { + *application.ServerBaseConfig + // LoadedHistoryLength is the maximum number of + // snapshots kept in memory. + LoadedHistoryLength uint64 `toml:"loaded_history_length"` + // Policies contains the server's CONIKS policies configuration. + Policies *Policies `toml:"policies"` + // Addresses contains the server's connections configuration. + Addresses []*Address `toml:"addresses"` +} + +var _ application.AppConfig = (*Config)(nil) + +// NewConfig initializes a new server configuration with the given +// server addresses, logger configuration, loaded history length and +// server application policies. +func NewConfig(addrs []*Address, logConfig *application.LoggerConfig, + loadedHistLen uint64, policies *Policies) *Config { + var conf = Config{ + ServerBaseConfig: &application.ServerBaseConfig{ + Logger: logConfig, + }, + LoadedHistoryLength: loadedHistLen, + Addresses: addrs, + Policies: policies, + } + + return &conf +} + +// Load initializes a server configuration from the +// corresponding config file. It reads the siging key pair and the VRF key +// pair into the Config instance and updates the path of +// TLS certificate files of each Address to absolute path. +func (conf *Config) Load(file string) error { + tmp, err := application.LoadConfig(file) + if err != nil { + return err + } + conf = tmp.(*Config) + + // load signing key + signPath := utils.ResolvePath(conf.Policies.SignKeyPath, file) + signKey, err := ioutil.ReadFile(signPath) + if err != nil { + return fmt.Errorf("Cannot read signing key: %v", err) + } + if len(signKey) != sign.PrivateKeySize { + return fmt.Errorf("Signing key must be 64 bytes (got %d)", len(signKey)) + } + + // load VRF key + vrfPath := utils.ResolvePath(conf.Policies.VRFKeyPath, file) + vrfKey, err := ioutil.ReadFile(vrfPath) + if err != nil { + return fmt.Errorf("Cannot read VRF key: %v", err) + } + if len(vrfKey) != vrf.PrivateKeySize { + return fmt.Errorf("VRF key must be 64 bytes (got %d)", len(vrfKey)) + } + + conf.ConfigFilePath = file + conf.Policies.vrfKey = vrfKey + conf.Policies.signKey = signKey + // also update path for TLS cert files + for _, addr := range conf.Addresses { + addr.TLSCertPath = utils.ResolvePath(addr.TLSCertPath, file) + addr.TLSKeyPath = utils.ResolvePath(addr.TLSKeyPath, file) + } + // logger config + conf.Logger.Path = utils.ResolvePath(conf.Logger.Path, file) + + return nil +} diff --git a/application/server/policies.go b/application/server/policies.go new file mode 100644 index 0000000..6e419b8 --- /dev/null +++ b/application/server/policies.go @@ -0,0 +1,31 @@ +package server + +import ( + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/protocol" +) + +// Policies contains a server's CONIKS policies configuration +// including paths to the VRF private key, the signing private +// key and the epoch deadline value in seconds. +type Policies struct { + EpochDeadline protocol.Timestamp `toml:"epoch_deadline"` + VRFKeyPath string `toml:"vrf_key_path"` + SignKeyPath string `toml:"sign_key_path"` // it should be a part of policies, see #47 + vrfKey vrf.PrivateKey + signKey sign.PrivateKey +} + +// NewPolicies initializes a new Policies struct. +func NewPolicies(epDeadline protocol.Timestamp, vrfKeyPath, + signKeyPath string, vrfKey vrf.PrivateKey, + signKey sign.PrivateKey) *Policies { + return &Policies{ + EpochDeadline: epDeadline, + VRFKeyPath: vrfKeyPath, + SignKeyPath: signKeyPath, + vrfKey: vrfKey, + signKey: signKey, + } +} diff --git a/application/server/server.go b/application/server/server.go new file mode 100644 index 0000000..47a48ab --- /dev/null +++ b/application/server/server.go @@ -0,0 +1,174 @@ +package server + +import ( + "time" + + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/protocol" + "github.com/coniks-sys/coniks-go/protocol/directory" +) + +// An Address describes a server's connection. +// It makes the server connections configurable +// so that a key server implementation can easily +// be run by a first-party identity provider or +// a third-party communication service. +// +// Allowing registration has to be specified explicitly for each connection. +// Other types of requests are allowed by default. +// One can think of a registration as a "write" to a key directory, +// while the other request types are "reads". +// So, by default, addresses are "read-only". +type Address struct { + *application.ServerAddress + AllowRegistration bool `toml:"allow_registration,omitempty"` +} + +// A ConiksServer represents a CONIKS key server. +// It wraps a ConiksDirectory with a network layer which +// handles requests/responses and their encoding/decoding. +// A ConiksServer also supports concurrent handling of requests and +// a mechanism to update the underlying ConiksDirectory automatically +// at regular time intervals. +type ConiksServer struct { + *application.ServerBase + dir *directory.ConiksDirectory + epochTimer *time.Timer +} + +var _ application.Server = (*ConiksServer)(nil) + +// NewConiksServer creates a new reference implementation of +// a CONIKS key server. +func NewConiksServer(conf *Config) *ConiksServer { + // determine this server's request permissions + perms := make(map[*application.ServerAddress]map[int]bool) + + for i := 0; i < len(conf.Addresses); i++ { + addr := conf.Addresses[i] + perms[addr.ServerAddress] = make(map[int]bool) + perms[addr.ServerAddress][protocol.KeyLookupType] = true + perms[addr.ServerAddress][protocol.KeyLookupInEpochType] = true + perms[addr.ServerAddress][protocol.MonitoringType] = true + perms[addr.ServerAddress][protocol.RegistrationType] = addr.AllowRegistration + } + + // create server instance + sb := application.NewServerBase(conf.ServerBaseConfig, "Listen", + perms) + + server := &ConiksServer{ + ServerBase: sb, + dir: directory.New( + conf.Policies.EpochDeadline, + conf.Policies.vrfKey, + conf.Policies.signKey, + conf.LoadedHistoryLength, + true), + epochTimer: time.NewTimer(time.Duration(conf.Policies.EpochDeadline) * time.Second), + } + + return server +} + +// EpochUpdate runs a CONIKS key server's directory epoch update procedure. +func (server *ConiksServer) EpochUpdate() { + server.epochUpdate() + server.WaitStopDone() +} + +// ConfigHotReload implements hot-reloading the configuration by +// listening for SIGUSR2 signal. +func (server *ConiksServer) ConfigHotReload() { + server.updatePolicies() + server.WaitStopDone() +} + +// HandleRequests validates the request message and passes it to the +// appropriate operation handler according to the request type. +func (server *ConiksServer) HandleRequests(req *protocol.Request) *protocol.Response { + switch req.Type { + case protocol.RegistrationType: + if msg, ok := req.Request.(*protocol.RegistrationRequest); ok { + return server.dir.Register(msg) + } + case protocol.KeyLookupType: + if msg, ok := req.Request.(*protocol.KeyLookupRequest); ok { + return server.dir.KeyLookup(msg) + } + case protocol.KeyLookupInEpochType: + if msg, ok := req.Request.(*protocol.KeyLookupInEpochRequest); ok { + return server.dir.KeyLookupInEpoch(msg) + } + case protocol.MonitoringType: + if msg, ok := req.Request.(*protocol.MonitoringRequest); ok { + return server.dir.Monitor(msg) + } + } + + return protocol.NewErrorResponse(protocol.ErrMalformedMessage) +} + +// Run implements the main functionality of the key server. +// It listens for all declared connections with corresponding +// permissions. +func (server *ConiksServer) Run(addrs []*Address) { + server.WaitStopAdd() + go server.EpochUpdate() + + hasRegistrationPerm := false + for i := 0; i < len(addrs); i++ { + addr := addrs[i] + hasRegistrationPerm = hasRegistrationPerm || addr.AllowRegistration + if addr.AllowRegistration { + server.Verb = "Accepting registrations" + } + + server.ListenAndHandle(addr.ServerAddress, server.HandleRequests) + } + + if !hasRegistrationPerm { + server.Logger().Warn("None of the addresses permit registration") + } + + server.WaitStopAdd() + go server.ConfigHotReload() +} + +func (server *ConiksServer) epochUpdate() { + for { + select { + case <-server.Stop(): + return + case <-server.epochTimer.C: + server.Lock() + server.dir.Update() + server.epochTimer.Reset(time.Duration(server.dir.EpochDeadline()) * time.Second) + server.Unlock() + } + } +} + +func (server *ConiksServer) updatePolicies() { + for { + select { + case <-server.Stop(): + return + case <-server.ReloadChan(): + // read server policies from config file + tmp, err := application.LoadConfig(server.ConfigFilePath()) + if err != nil { + // error occured while reading server config + // simply abort the reloading policies + // process + server.Logger().Error(err.Error()) + return + } + conf := tmp.(*Config) + server.Lock() + server.dir.SetPolicies(conf.Policies.EpochDeadline) + server.Unlock() + server.Logger().Info("Policies reloaded!") + } + } +} diff --git a/coniksserver/server_test.go b/application/server/server_test.go similarity index 87% rename from coniksserver/server_test.go rename to application/server/server_test.go index 8c1028e..236ba9b 100644 --- a/coniksserver/server_test.go +++ b/application/server/server_test.go @@ -1,4 +1,4 @@ -package coniksserver +package server import ( "bytes" @@ -8,11 +8,11 @@ import ( "testing" "time" - "github.com/coniks-sys/coniks-go/coniksserver/testutil" + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/application/testutil" "github.com/coniks-sys/coniks-go/crypto/sign" "github.com/coniks-sys/coniks-go/crypto/vrf" "github.com/coniks-sys/coniks-go/protocol" - "github.com/coniks-sys/coniks-go/utils" ) var registrationMsg = ` @@ -36,9 +36,19 @@ var keylookupMsg = ` } ` -func startServer(t *testing.T, epDeadline protocol.Timestamp, useBot bool, policiesPath string) (*ConiksServer, func()) { - dir, teardown := testutil.CreateTLSCertForTest(t) +func newTestTCPAddress(dir string) *application.ServerAddress { + return &application.ServerAddress{ + Address: testutil.PublicConnection, + TLSCertPath: path.Join(dir, "server.pem"), + TLSKeyPath: path.Join(dir, "server.key"), + } +} +// NewTestServer initializes a test CONIKS key server with the given +// epoch deadline, registration bot usage useBot, +// policies path, and directory. +func newTestServer(t *testing.T, epDeadline protocol.Timestamp, useBot bool, + policiesPath, dir string) (*ConiksServer, *Config) { signKey, err := sign.GenerateKey(nil) if err != nil { t.Fatal(err) @@ -51,34 +61,39 @@ func startServer(t *testing.T, epDeadline protocol.Timestamp, useBot bool, polic addrs := []*Address{ &Address{ - Address: testutil.PublicConnection, - TLSCertPath: path.Join(dir, "server.pem"), - TLSKeyPath: path.Join(dir, "server.key"), + ServerAddress: newTestTCPAddress(dir), AllowRegistration: !useBot, }, } if useBot { addrs = append(addrs, &Address{ - Address: testutil.LocalConnection, + ServerAddress: &application.ServerAddress{ + Address: testutil.LocalConnection, + }, AllowRegistration: useBot, }) } - conf := &ServerConfig{ + conf := &Config{ + ServerBaseConfig: &application.ServerBaseConfig{ + Logger: &application.LoggerConfig{ + Environment: "development", + Path: path.Join(dir, "coniksserver.log"), + }, + }, LoadedHistoryLength: 100, Addresses: addrs, - Policies: &ServerPolicies{ - EpochDeadline: epDeadline, - vrfKey: vrfKey, - signKey: signKey, - }, - Logger: &utils.LoggerConfig{ - Environment: "development", - Path: path.Join(dir, "coniksserver.log"), - }, + Policies: NewPolicies(epDeadline, "", "", vrfKey, + signKey), } - server := NewConiksServer(conf) + return NewConiksServer(conf), conf +} + +func startServer(t *testing.T, epDeadline protocol.Timestamp, useBot bool, policiesPath string) (*ConiksServer, func()) { + dir, teardown := testutil.CreateTLSCertForTest(t) + + server, conf := newTestServer(t, epDeadline, useBot, policiesPath, dir) server.Run(conf.Addresses) return server, func() { server.Shutdown() @@ -91,45 +106,6 @@ func TestServerStartStop(t *testing.T) { defer teardown() } -func TestResolveAddresses(t *testing.T) { - dir, teardown := testutil.CreateTLSCertForTest(t) - defer teardown() - - // test TCP network - addr := &Address{ - Address: testutil.PublicConnection, - TLSCertPath: path.Join(dir, "server.pem"), - TLSKeyPath: path.Join(dir, "server.key"), - } - ln, _, perms := resolveAndListen(addr) - defer ln.Close() - if perms[protocol.RegistrationType] != false { - t.Error("Expect disallowing registration permission.") - } - - // test Unix network - addr = &Address{ - Address: testutil.LocalConnection, - AllowRegistration: true, - } - ln, _, perms = resolveAndListen(addr) - defer ln.Close() - if perms[protocol.RegistrationType] != true { - t.Error("Expect allowing registration permission.") - } - - // test unknown network scheme - addr = &Address{ - Address: testutil.PublicConnection, - } - defer func() { - if r := recover(); r == nil { - t.Fatal("Expected resolveAndListen to panic.") - } - }() - resolveAndListen(addr) -} - func TestServerReloadPoliciesWithError(t *testing.T) { server, teardown := startServer(t, 1, true, "") defer teardown() @@ -204,7 +180,7 @@ func TestUpdateDirectory(t *testing.T) { str0 := server.dir.LatestSTR() rs := createMultiRegistrationRequests(10) for i := range rs { - req := server.handleOps(rs[i]) + req := server.HandleRequests(rs[i]) if req.Error != protocol.ReqSuccess { t.Fatal("Error while submitting registration request number", i, "to server") } @@ -239,11 +215,11 @@ func TestRegisterDuplicateUserInOneEpoch(t *testing.T) { defer teardown() r0 := createMultiRegistrationRequests(1)[0] r1 := createMultiRegistrationRequests(1)[0] - rev := server.handleOps(r0) + rev := server.HandleRequests(r0) if rev.Error != protocol.ReqSuccess { t.Fatal("Error while submitting registration request") } - rev = server.handleOps(r1) + rev = server.HandleRequests(r1) response, ok := rev.DirectoryResponse.(*protocol.DirectoryProof) if !ok { t.Fatal("Expect a directory proof response") @@ -263,13 +239,13 @@ func TestRegisterDuplicateUserInDifferentEpoches(t *testing.T) { server, teardown := startServer(t, 1, true, "") defer teardown() r0 := createMultiRegistrationRequests(1)[0] - rev := server.handleOps(r0) + rev := server.HandleRequests(r0) if rev.Error != protocol.ReqSuccess { t.Fatal("Error while submitting registration request") } timer := time.NewTimer(2 * time.Second) <-timer.C - rev = server.handleOps(r0) + rev = server.HandleRequests(r0) response, ok := rev.DirectoryResponse.(*protocol.DirectoryProof) if !ok { t.Fatal("Expect a directory proof response") diff --git a/application/serverbase.go b/application/serverbase.go new file mode 100644 index 0000000..34cd6c0 --- /dev/null +++ b/application/serverbase.go @@ -0,0 +1,293 @@ +package application + +import ( + "bytes" + "crypto/tls" + "io" + "net" + "net/url" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/coniks-sys/coniks-go/protocol" +) + +// A Server is a generic interface used to implement CONIKS key servers +// and auditors. +// CONIKS server's must implement a request handler, an epoch update +// procedure, and hot-reloading the configuration. +type Server interface { + EpochUpdate() + ConfigHotReload() + HandleRequests(*protocol.Request) *protocol.Response +} + +// A ServerBaseConfig contains configuration values +// which are read at initialization time from +// a TOML format configuration file. +type ServerBaseConfig struct { + Logger *LoggerConfig `toml:"logger"` + ConfigFilePath string +} + +// A ServerBase represents the base features needed to implement +// a CONIKS key server or auditor. +// It wraps a ConiksDirectory or AuditLog with a network layer which +// handles requests/responses and their encoding/decoding. +// A ServerBase also supports concurrent handling of requests. +type ServerBase struct { + Verb string + acceptableReqs map[*ServerAddress]map[int]bool + + logger *Logger + sync.RWMutex + + stop chan struct{} + waitStop sync.WaitGroup + waitCloseConn sync.WaitGroup + + configFilePath string + reloadChan chan os.Signal +} + +// A ServerAddress describes a server's connection. +// It supports two types of connections: a TCP connection ("tcp") +// and a Unix socket connection ("unix"). +// +// Additionally, TCP connections must use TLS for added security, +// and each is required to specify a TLS certificate and corresponding +// private key. +type ServerAddress struct { + // Address is formatted as a url: scheme://address. + Address string `toml:"address"` + // TLSCertPath is a path to the server's TLS Certificate, + // which has to be set if the connection is TCP. + TLSCertPath string `toml:"cert,omitempty"` + // TLSKeyPath is a path to the server's TLS private key, + // which has to be set if the connection is TCP. + TLSKeyPath string `toml:"key,omitempty"` +} + +// NewServerBase creates a new generic CONIKS-ready server base. +func NewServerBase(conf *ServerBaseConfig, listenVerb string, + perms map[*ServerAddress]map[int]bool) *ServerBase { + // create server instance + sb := new(ServerBase) + sb.Verb = listenVerb + sb.acceptableReqs = perms + sb.logger = NewLogger(conf.Logger) + sb.stop = make(chan struct{}) + sb.configFilePath = conf.ConfigFilePath + sb.reloadChan = make(chan os.Signal, 1) + signal.Notify(sb.reloadChan, syscall.SIGUSR2) + return sb +} + +// ListenAndHandle implements the main functionality of a CONIKS-ready +// server. It listens athe the given server address with corresponding +// permissions, and takes the specified pre- and post-Listening actions. +// It also supports hot-reloading the configuration by listening for +// SIGUSR2 signal. +func (sb *ServerBase) ListenAndHandle(addr *ServerAddress, + reqHandler func(req *protocol.Request) *protocol.Response) { + ln, tlsConfig := addr.resolveAndListen() + sb.waitStop.Add(1) + go func() { + sb.logger.Info(sb.Verb, "address", addr.Address) + sb.acceptRequests(addr, ln, tlsConfig, reqHandler) + sb.waitStop.Done() + }() +} + +func (addr *ServerAddress) resolveAndListen() (ln net.Listener, + tlsConfig *tls.Config) { + u, err := url.Parse(addr.Address) + if err != nil { + panic(err) + } + switch u.Scheme { + case "tcp": + // force to use TLS + cer, err := tls.LoadX509KeyPair(addr.TLSCertPath, addr.TLSKeyPath) + if err != nil { + panic(err) + } + tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + tcpaddr, err := net.ResolveTCPAddr(u.Scheme, u.Host) + if err != nil { + panic(err) + } + ln, err = net.ListenTCP(u.Scheme, tcpaddr) + if err != nil { + panic(err) + } + return + case "unix": + unixaddr, err := net.ResolveUnixAddr(u.Scheme, u.Path) + if err != nil { + panic(err) + } + ln, err = net.ListenUnix(u.Scheme, unixaddr) + if err != nil { + panic(err) + } + return + default: + panic("Unknown network type") + } +} + +func (sb *ServerBase) acceptRequests(addr *ServerAddress, ln net.Listener, + tlsConfig *tls.Config, + handler func(req *protocol.Request) *protocol.Response) { + defer ln.Close() + go func() { + <-sb.stop + if l, ok := ln.(interface { + SetDeadline(time.Time) error + }); ok { + l.SetDeadline(time.Now()) + } + }() + + for { + select { + case <-sb.stop: + sb.waitCloseConn.Wait() + return + default: + } + conn, err := ln.Accept() + if err != nil { + if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { + continue + } + sb.logger.Error(err.Error()) + continue + } + if _, ok := ln.(*net.TCPListener); ok { + conn = tls.Server(conn, tlsConfig) + } + sb.waitCloseConn.Add(1) + go func() { + sb.acceptClient(addr, conn, handler) + sb.waitCloseConn.Done() + }() + } +} + +// checkRequestType verifies that the server is allowed to handle +// the given Request message type at the given address. +// If reqType is not acceptable, checkRequestType() returns a +// protocol.ErrMalformedMessage, otherwise it returns. +func (sb *ServerBase) checkRequestType(addr *ServerAddress, + reqType int) error { + if !sb.acceptableReqs[addr][reqType] { + sb.logger.Error("Unacceptable message type", + "request type", reqType) + return protocol.ErrMalformedMessage + } + return nil +} + +func (sb *ServerBase) acceptClient(addr *ServerAddress, conn net.Conn, + handler func(req *protocol.Request) *protocol.Response) { + defer conn.Close() + conn.SetDeadline(time.Now().Add(5 * time.Second)) + + var buf bytes.Buffer + var response *protocol.Response + if _, err := io.CopyN(&buf, conn, 8192); err != nil && err != io.EOF { + sb.logger.Error(err.Error(), + "address", conn.RemoteAddr().String()) + return + } + + // unmarshalling + req, err := UnmarshalRequest(buf.Bytes()) + if err != nil { + response = malformedClientMsg(err) + } else { + if err := sb.checkRequestType(addr, req.Type); err != nil { + response = malformedClientMsg(err) + } else { + switch req.Type { + case protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: + sb.RLock() + default: + sb.Lock() + } + + response = handler(req) + + switch req.Type { + case protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: + sb.RUnlock() + default: + sb.Unlock() + } + + if response.Error != protocol.ReqSuccess { + sb.logger.Warn(response.Error.Error(), + "address", conn.RemoteAddr().String()) + } + } + } + + // marshalling + res, e := MarshalResponse(response) + if e != nil { + panic(e) + } + _, err = conn.Write([]byte(res)) + if err != nil { + sb.logger.Error(err.Error(), + "address", conn.RemoteAddr().String()) + return + } +} + +// TODO: Remove/refactor these getters. We would be happier if we didn't +// have to expose the WaitGroup to the server/auditor at all, and maybe +// we can export some of these other fields. + +// WaitStopAdd increments the server base's waitgroup counter. +func (sb *ServerBase) WaitStopAdd() { + sb.waitStop.Add(1) +} + +// WaitStopDone is a wrapper around waitgroup's Done(), which +// decrements the WaitGroup counter by one. +func (sb *ServerBase) WaitStopDone() { + sb.waitStop.Done() +} + +// Stop returns the server base's stop channel. +func (sb *ServerBase) Stop() chan struct{} { + return sb.stop +} + +// ReloadChan returns the server base's configuration reload channel. +func (sb *ServerBase) ReloadChan() chan os.Signal { + return sb.reloadChan +} + +// Logger returns the server base's logger instance. +func (sb *ServerBase) Logger() *Logger { + return sb.logger +} + +// ConfigFilePath returns the server base's config file path. +func (sb *ServerBase) ConfigFilePath() string { + return sb.configFilePath +} + +// Shutdown closes all of the server's connections and shuts down the server. +func (sb *ServerBase) Shutdown() error { + close(sb.stop) + sb.waitStop.Wait() + return nil +} diff --git a/application/serverbase_test.go b/application/serverbase_test.go new file mode 100644 index 0000000..1be5ab4 --- /dev/null +++ b/application/serverbase_test.go @@ -0,0 +1,40 @@ +package application + +import ( + "path" + "testing" + + "github.com/coniks-sys/coniks-go/application/testutil" +) + +func TestResolveAndListen(t *testing.T) { + dir, teardown := testutil.CreateTLSCertForTest(t) + defer teardown() + + // test TCP network + addr := &ServerAddress{ + Address: testutil.PublicConnection, + TLSCertPath: path.Join(dir, "server.pem"), + TLSKeyPath: path.Join(dir, "server.key"), + } + ln, _ := addr.resolveAndListen() + defer ln.Close() + + // test Unix network + addr = &ServerAddress{ + Address: testutil.LocalConnection, + } + ln, _ = addr.resolveAndListen() + defer ln.Close() + + // test unknown network scheme + addr = &ServerAddress{ + Address: testutil.PublicConnection, + } + defer func() { + if r := recover(); r == nil { + t.Fatal("Expected resolveAndListen to panic.") + } + }() + addr.resolveAndListen() +} diff --git a/coniksserver/testutil/testutil.go b/application/testutil/testutil.go similarity index 100% rename from coniksserver/testutil/testutil.go rename to application/testutil/testutil.go diff --git a/cli/command.go b/cli/command.go new file mode 100644 index 0000000..2ae255e --- /dev/null +++ b/cli/command.go @@ -0,0 +1,11 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +// cobraCommand is used to implement any type of cobra command +// for any of the CONIKS command-line tools and executables. +type cobraCommand interface { + Build() *cobra.Command +} diff --git a/coniksbots/README.md b/cli/coniksbot/README.md similarity index 87% rename from coniksbots/README.md rename to cli/coniksbot/README.md index c875d36..ce6144f 100644 --- a/coniksbots/README.md +++ b/cli/coniksbot/README.md @@ -2,15 +2,16 @@ ## Usage ``` -⇒ go install github.com/coniks-sys/coniks-go/coniksbots/cli +⇒ go install github.com/coniks-sys/coniks-go/cli/coniksbot ⇒ coniksbot -h Usage: coniksbot [command] Available Commands: - init Create a configuration file - run Run a CONIKS bot instance + init Create a configuration file for a CONIKS bot. + run Run a CONIKS bot instance. + version Print the version number of coniksbot. Flags: -h, --help help for coniksbot diff --git a/cli/coniksbot/coniksbot.go b/cli/coniksbot/coniksbot.go new file mode 100644 index 0000000..905d3ba --- /dev/null +++ b/cli/coniksbot/coniksbot.go @@ -0,0 +1,12 @@ +// Executable CONIKS registration proxy for Twitter usernames. See README for +// usage instructions. +package main + +import ( + "github.com/coniks-sys/coniks-go/cli" + "github.com/coniks-sys/coniks-go/cli/coniksbot/internal/cmd" +) + +func main() { + cli.Execute(cmd.RootCmd) +} diff --git a/cli/coniksbot/internal/cmd/init.go b/cli/coniksbot/internal/cmd/init.go new file mode 100644 index 0000000..7364cc0 --- /dev/null +++ b/cli/coniksbot/internal/cmd/init.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "log" + "path" + + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/application/bots" + "github.com/coniks-sys/coniks-go/cli" + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var initCmd = cli.NewInitCommand("CONIKS bot", mkBotConfig) + +func init() { + RootCmd.AddCommand(initCmd) + initCmd.Flags().StringP("dir", "d", ".", "Location of directory for storing generated files") +} + +func mkBotConfig(cmd *cobra.Command, args []string) { + dir := cmd.Flag("dir").Value.String() + file := path.Join(dir, "botconfig.toml") + + oauth := bots.TwitterOAuth{ + ConsumerKey: "secret", + ConsumerSecret: "secret", + AccessToken: "secret", + AccessSecret: "secret", + } + + conf := bots.NewTwitterConfig("/tmp/coniks.sock", "ConiksTorMess", + oauth) + if err := application.SaveConfig(file, conf); err != nil { + log.Print(err) + } +} diff --git a/cli/coniksbot/internal/cmd/root.go b/cli/coniksbot/internal/cmd/root.go new file mode 100644 index 0000000..a3cdec7 --- /dev/null +++ b/cli/coniksbot/internal/cmd/root.go @@ -0,0 +1,12 @@ +// Package cmd provides the CLI commands for a CONIKS +// account verification bot for Twitter accounts. +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +// RootCmd represents the base "coniksbot" command when called without any subcommands. +var RootCmd = cli.NewRootCommand("coniksbot", + "CONIKS bot for third-party account verification", + `CONIKS bot for third-party account verification`) diff --git a/coniksbots/cli/internal/cmd/run.go b/cli/coniksbot/internal/cmd/run.go similarity index 50% rename from coniksbots/cli/internal/cmd/run.go rename to cli/coniksbot/internal/cmd/run.go index b3a36d7..76ccbf2 100644 --- a/coniksbots/cli/internal/cmd/run.go +++ b/cli/coniksbot/internal/cmd/run.go @@ -4,32 +4,27 @@ import ( "os" "os/signal" - "github.com/coniks-sys/coniks-go/coniksbots" + "github.com/coniks-sys/coniks-go/application/bots" + "github.com/coniks-sys/coniks-go/cli" "github.com/spf13/cobra" ) // runCmd represents the run command -var runCmd = &cobra.Command{ - Use: "run", - Short: "Run a CONIKS bot instance", - Long: `Run a CONIKS bot instance +var runCmd = cli.NewRunCommand("CONIKS bot", + `Run a CONIKS bot instance. -This will look for config files with default names (botconfig.toml) +This will look for config files with default names in the current directory if not specified differently. - `, - Run: func(cmd *cobra.Command, args []string) { - config := cmd.Flag("config").Value.String() - run(config) - }, -} + `, run) func init() { RootCmd.AddCommand(runCmd) runCmd.Flags().StringP("config", "c", "botconfig.toml", "Path to bot configuration file") } -func run(confPath string) { - bot, err := coniksbots.NewTwitterBot(confPath) +func run(cmd *cobra.Command, args []string) { + confPath := cmd.Flag("config").Value.String() + bot, err := bots.NewTwitterBot(confPath) if err != nil { panic(err) } diff --git a/cli/coniksbot/internal/cmd/version.go b/cli/coniksbot/internal/cmd/version.go new file mode 100644 index 0000000..b5d8610 --- /dev/null +++ b/cli/coniksbot/internal/cmd/version.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +var versionCmd = cli.NewVersionCommand("coniksbot") + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/coniksclient/README.md b/cli/coniksclient/README.md similarity index 89% rename from coniksclient/README.md rename to cli/coniksclient/README.md index a9572e7..d4b2d40 100644 --- a/coniksclient/README.md +++ b/cli/coniksclient/README.md @@ -5,7 +5,7 @@ __Do not use your real public key or private key with this test client.__ ##### Install the test client ``` -⇒ go install github.com/coniks-sys/coniks-go/coniksclient/cli +⇒ go install github.com/coniks-sys/coniks-go/cli/coniksclient ⇒ coniksclient -h ________ _______ __ _ ___ ___ _ _______ | || || | | || || | | || | @@ -19,8 +19,9 @@ Usage: coniksclient [command] Available Commands: - init Creates a config file for the client. - run Run the test client. + init Create a configuration file for a CONIKS test client. + run Run a CONIKS test client instance. + version Print the version number of coniksclient. Use "coniksclient [command] --help" for more information about a command. ``` diff --git a/cli/coniksclient/coniksclient.go b/cli/coniksclient/coniksclient.go new file mode 100644 index 0000000..ac6b668 --- /dev/null +++ b/cli/coniksclient/coniksclient.go @@ -0,0 +1,12 @@ +// Executable CONIKS test client. See README for +// usage instructions. +package main + +import ( + "github.com/coniks-sys/coniks-go/cli" + "github.com/coniks-sys/coniks-go/cli/coniksclient/internal/cmd" +) + +func main() { + cli.Execute(cmd.RootCmd) +} diff --git a/coniksclient/cli/internal/cmd/common.go b/cli/coniksclient/internal/cmd/common.go similarity index 86% rename from coniksclient/cli/internal/cmd/common.go rename to cli/coniksclient/internal/cmd/common.go index bb6b74c..9b638d6 100644 --- a/coniksclient/cli/internal/cmd/common.go +++ b/cli/coniksclient/internal/cmd/common.go @@ -5,7 +5,7 @@ import ( "os" "time" - "github.com/coniks-sys/coniks-go/coniksclient" + "github.com/coniks-sys/coniks-go/application/client" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) @@ -27,9 +27,10 @@ specify where to look for the config with the --config flag. For example: testclient init --dir /etc/coniks/ ` -func loadConfigOrExit(cmd *cobra.Command) *coniksclient.Config { +func loadConfigOrExit(cmd *cobra.Command) *client.Config { config := cmd.Flag("config").Value.String() - conf, err := coniksclient.LoadConfig(config) + var conf *client.Config = &client.Config{} + err := conf.Load(config) if err != nil { fmt.Println(err) fmt.Print(configMissingUsage) diff --git a/cli/coniksclient/internal/cmd/init.go b/cli/coniksclient/internal/cmd/init.go new file mode 100644 index 0000000..34a1be3 --- /dev/null +++ b/cli/coniksclient/internal/cmd/init.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "fmt" + "os" + "path" + + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/application/client" + "github.com/coniks-sys/coniks-go/cli" + "github.com/spf13/cobra" +) + +var initCmd = cli.NewInitCommand("CONIKS test client", mkConfigOrExit) + +func init() { + RootCmd.AddCommand(initCmd) + initCmd.Flags().StringP("dir", "d", ".", + "Location of directory for storing generated files") +} + +func mkConfigOrExit(cmd *cobra.Command, args []string) { + dir := cmd.Flag("dir").Value.String() + file := path.Join(dir, "config.toml") + + conf := client.NewConfig("../../keyserver/coniksserver/sign.pub", + "tcp://127.0.0.1:3000", "tcp://127.0.0.1:3000") + + if err := application.SaveConfig(file, conf); err != nil { + fmt.Println("Couldn't save config. Error message: [" + + err.Error() + "]") + os.Exit(-1) + } +} diff --git a/coniksclient/cli/internal/cmd/root.go b/cli/coniksclient/internal/cmd/root.go similarity index 53% rename from coniksclient/cli/internal/cmd/root.go rename to cli/coniksclient/internal/cmd/root.go index 7f38df9..502cd54 100644 --- a/coniksclient/cli/internal/cmd/root.go +++ b/cli/coniksclient/internal/cmd/root.go @@ -1,18 +1,14 @@ package cmd import ( - "fmt" - "os" - - "github.com/spf13/cobra" + "github.com/coniks-sys/coniks-go/cli" ) // RootCmd represents the base "testclient" command when called without any // subcommands (register, lookup, ...). -var RootCmd = &cobra.Command{ - Use: "coniksclient", - Short: "CONIKS client reference implementation in Go", - Long: ` +var RootCmd = cli.NewRootCommand("coniksclient", + "CONIKS test client reference implementation in Go", + ` ________ _______ __ _ ___ ___ _ _______ | || || | | || || | | || | | || _ || |_| || || |_| || _____| @@ -20,14 +16,4 @@ ________ _______ __ _ ___ ___ _ _______ | _|| |_| || _ || || |_ |_____ | | |_ | || | | || || _ | _____| | |_______||_______||_| |__||___||___| |_||_______| -`, -} - -// Execute adds all child commands to the root command sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(-1) - } -} +`) diff --git a/coniksclient/cli/internal/cmd/run.go b/cli/coniksclient/internal/cmd/run.go similarity index 86% rename from coniksclient/cli/internal/cmd/run.go rename to cli/coniksclient/internal/cmd/run.go index 536d4ed..a62dd24 100644 --- a/coniksclient/cli/internal/cmd/run.go +++ b/cli/coniksclient/internal/cmd/run.go @@ -7,8 +7,10 @@ import ( "strconv" "strings" - "github.com/coniks-sys/coniks-go/coniksclient" - "github.com/coniks-sys/coniks-go/coniksserver/testutil" + "github.com/coniks-sys/coniks-go/application" + clientapp "github.com/coniks-sys/coniks-go/application/client" + "github.com/coniks-sys/coniks-go/application/testutil" + "github.com/coniks-sys/coniks-go/cli" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/client" "github.com/spf13/cobra" @@ -28,14 +30,7 @@ const help = "- register [name] [key]:\r\n" + "- exit, q:\r\n" + " Close the REPL and exit the client." -var runCmd = &cobra.Command{ - Use: "run", - Short: "Run the test client.", - Long: "Run gives you a REPL, so that you can invoke commands to perform CONIKS operations including registration and key lookup. Currently, it supports:\n" + help, - Run: func(cmd *cobra.Command, args []string) { - run(cmd) - }, -} +var runCmd = cli.NewRunCommand("CONIKS test client", "Run gives you a REPL, so that you can invoke commands to perform CONIKS operations including registration and key lookup. Currently, it supports:\n"+help, run) func init() { RootCmd.AddCommand(runCmd) @@ -44,7 +39,7 @@ func init() { runCmd.Flags().BoolP("debug", "d", false, "Turn on debugging mode") } -func run(cmd *cobra.Command) { +func run(cmd *cobra.Command, args []string) { isDebugging, _ := strconv.ParseBool(cmd.Flag("debug").Value.String()) conf := loadConfigOrExit(cmd) cc := client.New(nil, true, conf.SigningPubKey) @@ -110,8 +105,8 @@ func run(cmd *cobra.Command) { } } -func register(cc *client.ConsistencyChecks, conf *coniksclient.Config, name string, key string) string { - req, err := coniksclient.CreateRegistrationMsg(name, []byte(key)) +func register(cc *client.ConsistencyChecks, conf *clientapp.Config, name string, key string) string { + req, err := clientapp.CreateRegistrationMsg(name, []byte(key)) if err != nil { return ("Couldn't marshal registration request!") } @@ -138,7 +133,7 @@ func register(cc *client.ConsistencyChecks, conf *coniksclient.Config, name stri return ("Invalid config!") } - response := coniksclient.UnmarshalResponse(protocol.RegistrationType, res) + response := application.UnmarshalResponse(protocol.RegistrationType, res) err = cc.HandleResponse(protocol.RegistrationType, response, name, []byte(key)) switch err { case protocol.CheckBadSTR: @@ -168,8 +163,8 @@ func register(cc *client.ConsistencyChecks, conf *coniksclient.Config, name stri return "" } -func keyLookup(cc *client.ConsistencyChecks, conf *coniksclient.Config, name string) string { - req, err := coniksclient.CreateKeyLookupMsg(name) +func keyLookup(cc *client.ConsistencyChecks, conf *clientapp.Config, name string) string { + req, err := clientapp.CreateKeyLookupMsg(name) if err != nil { return ("Couldn't marshal key lookup request!") } @@ -191,7 +186,7 @@ func keyLookup(cc *client.ConsistencyChecks, conf *coniksclient.Config, name str return ("Invalid config!") } - response := coniksclient.UnmarshalResponse(protocol.KeyLookupType, res) + response := application.UnmarshalResponse(protocol.KeyLookupType, res) if key, ok := cc.Bindings[name]; ok { err = cc.HandleResponse(protocol.KeyLookupType, response, name, []byte(key)) } else { diff --git a/cli/coniksclient/internal/cmd/version.go b/cli/coniksclient/internal/cmd/version.go new file mode 100644 index 0000000..c61bd91 --- /dev/null +++ b/cli/coniksclient/internal/cmd/version.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +var versionCmd = cli.NewVersionCommand("coniksclient") + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/coniksserver/README.md b/cli/coniksserver/README.md similarity index 91% rename from coniksserver/README.md rename to cli/coniksserver/README.md index 557c0aa..620f6b3 100644 --- a/coniksserver/README.md +++ b/cli/coniksserver/README.md @@ -2,7 +2,7 @@ ## Usage ``` -⇒ go install github.com/coniks-sys/coniks-go/coniksserver/cli +⇒ go install github.com/coniks-sys/coniks-go/cli/coniksserver ⇒ coniksserver -h _______ _______ __ _ ___ ___ _ _______ | || || | | || || | | || | @@ -16,8 +16,9 @@ Usage: coniksserver [command] Available Commands: - init Create a configuration file and generate all keys - run Run a CONIKS server instance + init Create a configuration file for a CONIKS server. + run Run a CONIKS server instance. + version Print the version number of coniksserver. Flags: -h, --help help for coniksserver diff --git a/cli/coniksserver/coniksserver.go b/cli/coniksserver/coniksserver.go new file mode 100644 index 0000000..ef21e76 --- /dev/null +++ b/cli/coniksserver/coniksserver.go @@ -0,0 +1,12 @@ +// Executable CONIKS key server. See README for +// usage instructions. +package main + +import ( + "github.com/coniks-sys/coniks-go/cli" + "github.com/coniks-sys/coniks-go/cli/coniksserver/internal/cmd" +) + +func main() { + cli.Execute(cmd.RootCmd) +} diff --git a/coniksserver/cli/internal/cmd/init.go b/cli/coniksserver/internal/cmd/init.go similarity index 50% rename from coniksserver/cli/internal/cmd/init.go rename to cli/coniksserver/internal/cmd/init.go index ef763bf..e9b3903 100644 --- a/coniksserver/cli/internal/cmd/init.go +++ b/cli/coniksserver/internal/cmd/init.go @@ -1,14 +1,14 @@ package cmd import ( - "bytes" "log" "path" "strconv" - "github.com/BurntSushi/toml" - "github.com/coniks-sys/coniks-go/coniksserver" - "github.com/coniks-sys/coniks-go/coniksserver/testutil" + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/application/server" + "github.com/coniks-sys/coniks-go/application/testutil" + "github.com/coniks-sys/coniks-go/cli" "github.com/coniks-sys/coniks-go/crypto/sign" "github.com/coniks-sys/coniks-go/crypto/vrf" "github.com/coniks-sys/coniks-go/utils" @@ -16,22 +16,7 @@ import ( ) // initCmd represents the init command -var initCmd = &cobra.Command{ - Use: "init", - Short: "Create a configuration file and generate all keys", - Long: `Create a configuration file and generate all keys for signing and VRF`, - Run: func(cmd *cobra.Command, args []string) { - dir := cmd.Flag("dir").Value.String() - mkConfig(dir) - mkSigningKey(dir) - mkVrfKey(dir) - - cert, err := strconv.ParseBool(cmd.Flag("cert").Value.String()) - if err == nil && cert { - testutil.CreateTLSCert(dir) - } - }, -} +var initCmd = cli.NewInitCommand("CONIKS key server", initRunFunc) func init() { RootCmd.AddCommand(initCmd) @@ -39,43 +24,54 @@ func init() { initCmd.Flags().BoolP("cert", "c", false, "Generate self-signed ssl keys/cert with sane defaults") } +func initRunFunc(cmd *cobra.Command, args []string) { + dir := cmd.Flag("dir").Value.String() + mkConfig(dir) + mkSigningKey(dir) + mkVrfKey(dir) + + cert, err := strconv.ParseBool(cmd.Flag("cert").Value.String()) + if err == nil && cert { + testutil.CreateTLSCert(dir) + } +} + func mkConfig(dir string) { file := path.Join(dir, "config.toml") - addrs := []*coniksserver.Address{ - &coniksserver.Address{ - Address: "unix:///tmp/coniks.sock", + addrs := []*server.Address{ + &server.Address{ + ServerAddress: &application.ServerAddress{ + Address: "unix:///tmp/coniks.sock", + }, AllowRegistration: true, }, - &coniksserver.Address{ - Address: "tcp://0.0.0.0:3000", - TLSCertPath: "server.pem", - TLSKeyPath: "server.key", + &server.Address{ + ServerAddress: &application.ServerAddress{ + Address: "tcp://0.0.0.0:3000", + TLSCertPath: "server.pem", + TLSKeyPath: "server.key", + }, }, } - var conf = coniksserver.ServerConfig{ - LoadedHistoryLength: 1000000, - Addresses: addrs, - Policies: &coniksserver.ServerPolicies{ - EpochDeadline: 60, - VRFKeyPath: "vrf.priv", - SignKeyPath: "sign.priv", - }, - Logger: &utils.LoggerConfig{ - EnableStacktrace: true, - Environment: "development", - Path: "coniksserver.log", - }, + + logger := &application.LoggerConfig{ + EnableStacktrace: true, + Environment: "development", + Path: "coniksserver.log", } - var confBuf bytes.Buffer + policies := &server.Policies{ + EpochDeadline: 60, + VRFKeyPath: "vrf.priv", + SignKeyPath: "sign.priv", + } + + conf := server.NewConfig(addrs, logger, 1000000, policies) + err := application.SaveConfig(file, conf) - e := toml.NewEncoder(&confBuf) - err := e.Encode(conf) if err != nil { log.Println(err) - return } - utils.WriteFile(file, confBuf.Bytes(), 0644) } func mkSigningKey(dir string) { diff --git a/coniksserver/cli/internal/cmd/root.go b/cli/coniksserver/internal/cmd/root.go similarity index 59% rename from coniksserver/cli/internal/cmd/root.go rename to cli/coniksserver/internal/cmd/root.go index 4e702ff..5d74d01 100644 --- a/coniksserver/cli/internal/cmd/root.go +++ b/cli/coniksserver/internal/cmd/root.go @@ -2,17 +2,13 @@ package cmd import ( - "fmt" - "os" - - "github.com/spf13/cobra" + "github.com/coniks-sys/coniks-go/cli" ) // RootCmd represents the base "coniksserver" command when called without any subcommands. -var RootCmd = &cobra.Command{ - Use: "coniksserver", - Short: "CONIKS reference implementation in Go", - Long: ` +var RootCmd = cli.NewRootCommand("coniksserver", + "CONIKS server reference implementation in Go", + ` ________ _______ __ _ ___ ___ _ _______ | || || | | || || | | || | | || _ || |_| || || |_| || _____| @@ -20,14 +16,4 @@ ________ _______ __ _ ___ ___ _ _______ | _|| |_| || _ || || |_ |_____ | | |_ | || | | || || _ | _____| | |_______||_______||_| |__||___||___| |_||_______| -`, -} - -// Execute adds all subcommands (i.e. "init" and "run") to the RootCmd -// and sets their flags appropriately. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(-1) - } -} +`) diff --git a/coniksserver/cli/internal/cmd/run.go b/cli/coniksserver/internal/cmd/run.go similarity index 60% rename from coniksserver/cli/internal/cmd/run.go rename to cli/coniksserver/internal/cmd/run.go index cfff46e..2ef7472 100644 --- a/coniksserver/cli/internal/cmd/run.go +++ b/cli/coniksserver/internal/cmd/run.go @@ -8,29 +8,18 @@ import ( "path" "strconv" - "github.com/coniks-sys/coniks-go/coniksserver" + "github.com/coniks-sys/coniks-go/application/server" + "github.com/coniks-sys/coniks-go/cli" "github.com/spf13/cobra" ) // runCmd represents the run command -var runCmd = &cobra.Command{ - Use: "run", - Short: "Run a CONIKS server instance", - Long: `Run a CONIKS server instance +var runCmd = cli.NewRunCommand("CONIKS server", + `Run a CONIKS server instance. -This will look for config files with default names (config.toml) +This will look for config files with default names in the current directory if not specified differently. - `, - Run: func(cmd *cobra.Command, args []string) { - config := cmd.Flag("config").Value.String() - pid, _ := strconv.ParseBool(cmd.Flag("pid").Value.String()) - // ignore the error here since it is handled by the flag parser. - if pid { - writePID() - } - run(config) - }, -} + `, run) func init() { RootCmd.AddCommand(runCmd) @@ -38,12 +27,19 @@ func init() { runCmd.Flags().BoolP("pid", "p", false, "Write down the process id to coniks.pid in the current working directory") } -func run(confPath string) { - conf, err := coniksserver.LoadServerConfig(confPath) +func run(cmd *cobra.Command, args []string) { + confPath := cmd.Flag("config").Value.String() + pid, _ := strconv.ParseBool(cmd.Flag("pid").Value.String()) + // ignore the error here since it is handled by the flag parser. + if pid { + writePID() + } + var conf *server.Config = &server.Config{} + err := conf.Load(confPath) if err != nil { log.Fatal(err) } - serv := coniksserver.NewConiksServer(conf) + serv := server.NewConiksServer(conf) // run the server until receiving an interrupt signal serv.Run(conf.Addresses) diff --git a/cli/coniksserver/internal/cmd/version.go b/cli/coniksserver/internal/cmd/version.go new file mode 100644 index 0000000..8ba1bae --- /dev/null +++ b/cli/coniksserver/internal/cmd/version.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +var versionCmd = cli.NewVersionCommand("coniksserver") + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/cli/doc.go b/cli/doc.go new file mode 100644 index 0000000..f9626d8 --- /dev/null +++ b/cli/doc.go @@ -0,0 +1,5 @@ +// Package cli provides an API to implement various commands for any +// kind of CONIKS command-line application/executable. +// Currently, cli supports Cobra-based CLI applications, but could be +// expanded to support other CLI commanders. +package cli diff --git a/cli/init.go b/cli/init.go new file mode 100644 index 0000000..bed961d --- /dev/null +++ b/cli/init.go @@ -0,0 +1,37 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +// An InitCommand is used to create a CONIKS executable's +// configuration. +type initCommand struct { + appName string + runFunc func(cmd *cobra.Command, args []string) +} + +var _ cobraCommand = (*initCommand)(nil) + +// NewInitCommand constructs a new InitCommand for the given +// exectuable's appName and the runFunc implementing +// the initialization command. +func NewInitCommand(appName string, runFunc func(cmd *cobra.Command, args []string)) *cobra.Command { + initCmd := &initCommand{ + appName: appName, + runFunc: runFunc, + } + return initCmd.Build() +} + +// Build constructs the cobra.Command according to the +// InitCommand's settings. +func (initCmd *initCommand) Build() *cobra.Command { + cmd := cobra.Command{ + Use: "init", + Short: "Create a configuration file for a " + initCmd.appName + ".", + Long: `Create a configuration file for a ` + initCmd.appName + `.`, + Run: initCmd.runFunc, + } + return &cmd +} diff --git a/cli/root.go b/cli/root.go new file mode 100644 index 0000000..8a676ae --- /dev/null +++ b/cli/root.go @@ -0,0 +1,49 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// A rootCommand is used to create a CONIKS executable's +// root command that executes all subcommands. +type rootCommand struct { + use string + short string + long string +} + +var _ cobraCommand = (*rootCommand)(nil) + +// NewRootCommand constructs a new RootCommand for the given +// exectuable's use, short and long descriptions. +func NewRootCommand(use, short, long string) *cobra.Command { + rootCmd := &rootCommand{ + use: use, + short: short, + long: long, + } + return rootCmd.Build() +} + +// Build constructs the cobra.Command according to the +// RootCommand's settings. +func (rootCmd *rootCommand) Build() *cobra.Command { + cmd := cobra.Command{ + Use: rootCmd.use, + Short: rootCmd.short, + Long: rootCmd.long, + } + return &cmd +} + +// Execute adds all subcommands (i.e. "init" and "run") to the RootCmd +// and sets their flags appropriately. +func Execute(rootCmd *cobra.Command) { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} diff --git a/cli/run.go b/cli/run.go new file mode 100644 index 0000000..8ebebf9 --- /dev/null +++ b/cli/run.go @@ -0,0 +1,39 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +// A runCommand is used to create a CONIKS executable's +// main functionality. +type runCommand struct { + appName string + long string + runFunc func(cmd *cobra.Command, args []string) +} + +var _ cobraCommand = (*runCommand)(nil) + +// NewRunCommand constructs a new RunCommand for the given +// exectuable's appName and the runFunc implementing +// the main functionality run command. +func NewRunCommand(appName, long string, runFunc func(cmd *cobra.Command, args []string)) *cobra.Command { + runCmd := &runCommand{ + appName: appName, + long: long, + runFunc: runFunc, + } + return runCmd.Build() +} + +// Build constructs the cobra.Command according to the +// RunCommand's settings. +func (runCmd *runCommand) Build() *cobra.Command { + cmd := cobra.Command{ + Use: "run", + Short: "Run a " + runCmd.appName + " instance.", + Long: runCmd.long, + Run: runCmd.runFunc, + } + return &cmd +} diff --git a/cli/version.go b/cli/version.go new file mode 100644 index 0000000..9979fb6 --- /dev/null +++ b/cli/version.go @@ -0,0 +1,40 @@ +package cli + +import ( + "fmt" + + "github.com/coniks-sys/coniks-go/internal" + "github.com/spf13/cobra" +) + +// A versionCommand is used to display a CONIKS executable's +// version. +type versionCommand struct { + appName string +} + +var _ cobraCommand = (*versionCommand)(nil) + +// NewVersionCommand constructs a new VersionCommand for the given +// exectuable's appName. +func NewVersionCommand(appName string) *cobra.Command { + versCmd := &versionCommand{ + appName: appName, + } + return versCmd.Build() +} + +// Build constructs the cobra.Command according to the +// VersionCommand's settings. +func (versCmd *versionCommand) Build() *cobra.Command { + cmd := cobra.Command{ + Use: "version", + Short: "Print the version number of " + versCmd.appName + ".", + Long: `Print the version number of ` + versCmd.appName + `.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("All software has versions. This is " + versCmd.appName + "'s:") + fmt.Println(versCmd.appName + " v" + internal.Version) + }, + } + return &cmd +} diff --git a/coniksbots/cli/coniksbot.go b/coniksbots/cli/coniksbot.go deleted file mode 100644 index 06de6b3..0000000 --- a/coniksbots/cli/coniksbot.go +++ /dev/null @@ -1,9 +0,0 @@ -// Executable CONIKS registration proxy for Twitter usernames. See README for -// usage instructions. -package main - -import "github.com/coniks-sys/coniks-go/coniksbots/cli/internal/cmd" - -func main() { - cmd.Execute() -} diff --git a/coniksbots/cli/internal/cmd/init.go b/coniksbots/cli/internal/cmd/init.go deleted file mode 100644 index 4a1a0df..0000000 --- a/coniksbots/cli/internal/cmd/init.go +++ /dev/null @@ -1,52 +0,0 @@ -package cmd - -import ( - "bytes" - "log" - "path" - - "github.com/BurntSushi/toml" - "github.com/coniks-sys/coniks-go/coniksbots" - "github.com/coniks-sys/coniks-go/utils" - "github.com/spf13/cobra" -) - -// initCmd represents the init command -var initCmd = &cobra.Command{ - Use: "init", - Short: "Create a configuration file for CONIKS bot", - Long: `Create a configuration file for CONIKS bot`, - Run: func(cmd *cobra.Command, args []string) { - dir := cmd.Flag("dir").Value.String() - mkBotConfig(dir) - }, -} - -func init() { - RootCmd.AddCommand(initCmd) - initCmd.Flags().StringP("dir", "d", ".", "Location of directory for storing generated files") -} - -func mkBotConfig(dir string) { - file := path.Join(dir, "botconfig.toml") - var conf = coniksbots.TwitterConfig{ - CONIKSAddress: "/tmp/coniks.sock", - Handle: "ConiksTorMess", - TwitterOAuth: coniksbots.TwitterOAuth{ - ConsumerKey: "secret", - ConsumerSecret: "secret", - AccessToken: "secret", - AccessSecret: "secret", - }, - } - - var confBuf bytes.Buffer - - e := toml.NewEncoder(&confBuf) - err := e.Encode(conf) - if err != nil { - log.Print(err) - return - } - utils.WriteFile(file, confBuf.Bytes(), 0644) -} diff --git a/coniksbots/cli/internal/cmd/root.go b/coniksbots/cli/internal/cmd/root.go deleted file mode 100644 index b5b850f..0000000 --- a/coniksbots/cli/internal/cmd/root.go +++ /dev/null @@ -1,26 +0,0 @@ -// Package cmd provides the CLI commands for a CONIKS -// account verification bot for Twitter accounts. -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -// RootCmd represents the base "coniksbot" command when called without any subcommands. -var RootCmd = &cobra.Command{ - Use: "coniksbot", - Short: "CONIKS bot for third-party account verification", - Long: `CONIKS bot for third-party account verification`, -} - -// Execute adds all subcommands (i.e. "init" and "run") to the RootCmd -// and sets their flags appropriately. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(-1) - } -} diff --git a/coniksbots/cli/internal/cmd/version.go b/coniksbots/cli/internal/cmd/version.go deleted file mode 100644 index 68b5d2a..0000000 --- a/coniksbots/cli/internal/cmd/version.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/coniks-sys/coniks-go/internal" - "github.com/spf13/cobra" -) - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of coniksbot.", - Long: `Print the version number of coniksbot.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("All software has versions. This is coniksbot's:") - fmt.Println("coniksbot v" + internal.Version) - }, -} - -func init() { - RootCmd.AddCommand(versionCmd) -} diff --git a/coniksclient/cli/coniksclient.go b/coniksclient/cli/coniksclient.go deleted file mode 100644 index 998e480..0000000 --- a/coniksclient/cli/coniksclient.go +++ /dev/null @@ -1,11 +0,0 @@ -// Executable CONIKS test client. See README for -// usage instructions. -package main - -import ( - "github.com/coniks-sys/coniks-go/coniksclient/cli/internal/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/coniksclient/cli/internal/cmd/init.go b/coniksclient/cli/internal/cmd/init.go deleted file mode 100644 index c65271d..0000000 --- a/coniksclient/cli/internal/cmd/init.go +++ /dev/null @@ -1,61 +0,0 @@ -package cmd - -import ( - "fmt" - "path" - - "bytes" - "os" - - "github.com/BurntSushi/toml" - "github.com/coniks-sys/coniks-go/coniksclient" - "github.com/coniks-sys/coniks-go/utils" - "github.com/spf13/cobra" -) - -var initCmd = &cobra.Command{ - Use: "init", - Short: "Creates a config file for the client.", - Long: `Creates a file config.toml in the current working directory with -the following content: - -sign_pubkey_path = "../../keyserver/coniksserver/sign.pub" -registration_address = "tcp://127.0.0.1:3000" -address = "tcp://127.0.0.1:3000" - -If the keyserver's public keys are somewhere else, you will have to modify the -config file accordingly. -`, - Run: func(cmd *cobra.Command, args []string) { - dir := cmd.Flag("dir").Value.String() - mkConfigOrExit(dir) - }, -} - -func init() { - RootCmd.AddCommand(initCmd) - initCmd.Flags().StringP("dir", "d", ".", - "Location of directory for storing generated files") -} - -func mkConfigOrExit(dir string) { - file := path.Join(dir, "config.toml") - var conf = coniksclient.Config{ - SignPubkeyPath: "../../keyserver/coniksserver/sign.pub", - RegAddress: "tcp://127.0.0.1:3000", - Address: "tcp://127.0.0.1:3000", - } - - var confBuf bytes.Buffer - enc := toml.NewEncoder(&confBuf) - if err := enc.Encode(conf); err != nil { - fmt.Println("Coulnd't encode config. Error message: [" + - err.Error() + "]") - os.Exit(-1) - } - if err := utils.WriteFile(file, confBuf.Bytes(), 0644); err != nil { - fmt.Println("Coulnd't write config. Error message: [" + - err.Error() + "]") - os.Exit(-1) - } -} diff --git a/coniksclient/cli/internal/cmd/version.go b/coniksclient/cli/internal/cmd/version.go deleted file mode 100644 index 8bc8ddf..0000000 --- a/coniksclient/cli/internal/cmd/version.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/coniks-sys/coniks-go/internal" - "github.com/spf13/cobra" -) - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of coniksclient.", - Long: `Print the version number of coniksclient.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("All software has versions. This is coniksclient's:") - fmt.Println("coniksclient v" + internal.Version) - }, -} - -func init() { - RootCmd.AddCommand(versionCmd) -} diff --git a/coniksclient/doc.go b/coniksclient/doc.go deleted file mode 100644 index 8d684f7..0000000 --- a/coniksclient/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -/* -Package coniksclient provides an executable reference implementation of -a simple client for the CONIKS key management system. - -This test client is designed to communicate with the basic implementation -of a CONIKS server. -*/ -package coniksclient diff --git a/coniksclient/encoding.go b/coniksclient/encoding.go deleted file mode 100644 index b57304c..0000000 --- a/coniksclient/encoding.go +++ /dev/null @@ -1,75 +0,0 @@ -package coniksclient - -import ( - "encoding/json" - - "github.com/coniks-sys/coniks-go/protocol" -) - -// UnmarshalResponse decodes the given message into a protocol.Response -// according to the given request type t. The request types are integer -// constants defined in the protocol package. -func UnmarshalResponse(t int, msg []byte) *protocol.Response { - type Response struct { - Error protocol.ErrorCode - DirectoryResponse json.RawMessage - } - var res Response - if err := json.Unmarshal(msg, &res); err != nil { - return &protocol.Response{ - Error: protocol.ErrMalformedMessage, - } - } - - // DirectoryResponse is omitempty for the places - // where Error is in Errors - if res.DirectoryResponse == nil { - if !protocol.Errors[res.Error] { - return &protocol.Response{ - Error: protocol.ErrMalformedMessage, - } - } - return &protocol.Response{ - Error: res.Error, - } - } - - switch t { - case protocol.RegistrationType, protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: - response := new(protocol.DirectoryProof) - if err := json.Unmarshal(res.DirectoryResponse, &response); err != nil { - return &protocol.Response{ - Error: protocol.ErrMalformedMessage, - } - } - return &protocol.Response{ - Error: res.Error, - DirectoryResponse: response, - } - default: - panic("Unknown request type") - } -} - -// CreateRegistrationMsg returns a JSON encoding of -// a protocol.RegistrationRequest for the given (name, key) pair. -func CreateRegistrationMsg(name string, key []byte) ([]byte, error) { - return json.Marshal(&protocol.Request{ - Type: protocol.RegistrationType, - Request: &protocol.RegistrationRequest{ - Username: name, - Key: key, - }, - }) -} - -// CreateKeyLookupMsg returns a JSON encoding of -// a protocol.KeyLookupRequest for the given name. -func CreateKeyLookupMsg(name string) ([]byte, error) { - return json.Marshal(&protocol.Request{ - Type: protocol.KeyLookupType, - Request: &protocol.KeyLookupRequest{ - Username: name, - }, - }) -} diff --git a/coniksserver/cli/coniksserver.go b/coniksserver/cli/coniksserver.go deleted file mode 100644 index d11dedf..0000000 --- a/coniksserver/cli/coniksserver.go +++ /dev/null @@ -1,9 +0,0 @@ -// Executable CONIKS key server. See README for -// usage instructions. -package main - -import "github.com/coniks-sys/coniks-go/coniksserver/cli/internal/cmd" - -func main() { - cmd.Execute() -} diff --git a/coniksserver/cli/internal/cmd/version.go b/coniksserver/cli/internal/cmd/version.go deleted file mode 100644 index dd9314d..0000000 --- a/coniksserver/cli/internal/cmd/version.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/coniks-sys/coniks-go/internal" - "github.com/spf13/cobra" -) - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of coniksserver.", - Long: `Print the version number of coniksserver.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("All software has versions. This is coniksserver's:") - fmt.Println("coniksserver v" + internal.Version) - }, -} - -func init() { - RootCmd.AddCommand(versionCmd) -} diff --git a/coniksserver/doc.go b/coniksserver/doc.go deleted file mode 100644 index 7387b16..0000000 --- a/coniksserver/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -/* -Package coniksserver provides an executable reference implementation of -a server for the CONIKS key management system. -*/ -package coniksserver diff --git a/coniksserver/encoding.go b/coniksserver/encoding.go deleted file mode 100644 index 3ae60ad..0000000 --- a/coniksserver/encoding.go +++ /dev/null @@ -1,45 +0,0 @@ -// Defines methods/functions to encode/decode messages between client -// and server. Currently this module supports JSON marshal/unmarshal only. -// Protobuf will be supported in the future. - -package coniksserver - -import ( - "encoding/json" - - "github.com/coniks-sys/coniks-go/protocol" -) - -// MarshalResponse returns a JSON encoding of the server's response. -func MarshalResponse(response *protocol.Response) ([]byte, error) { - return json.Marshal(response) -} - -// UnmarshalRequest parses a JSON-encoded request msg and -// creates the corresponding protocol.Request, which will be handled -// by the server. -func UnmarshalRequest(msg []byte) (*protocol.Request, error) { - var content json.RawMessage - req := protocol.Request{ - Request: &content, - } - if err := json.Unmarshal(msg, &req); err != nil { - return nil, err - } - var request interface{} - switch req.Type { - case protocol.RegistrationType: - request = new(protocol.RegistrationRequest) - case protocol.KeyLookupType: - request = new(protocol.KeyLookupRequest) - case protocol.KeyLookupInEpochType: - request = new(protocol.KeyLookupInEpochRequest) - case protocol.MonitoringType: - request = new(protocol.MonitoringRequest) - } - if err := json.Unmarshal(content, &request); err != nil { - return nil, err - } - req.Request = request - return &req, nil -} diff --git a/coniksserver/listener.go b/coniksserver/listener.go deleted file mode 100644 index 0ea244a..0000000 --- a/coniksserver/listener.go +++ /dev/null @@ -1,144 +0,0 @@ -package coniksserver - -import ( - "bytes" - "crypto/tls" - "io" - "net" - "time" - - "github.com/coniks-sys/coniks-go/protocol" -) - -func (server *ConiksServer) handleRequests(ln net.Listener, tlsConfig *tls.Config, - handler func(req *protocol.Request) *protocol.Response) { - defer ln.Close() - go func() { - <-server.stop - if l, ok := ln.(interface { - SetDeadline(time.Time) error - }); ok { - l.SetDeadline(time.Now()) - } - }() - - for { - select { - case <-server.stop: - server.waitCloseConn.Wait() - return - default: - } - conn, err := ln.Accept() - if err != nil { - if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { - continue - } - server.logger.Error(err.Error()) - continue - } - if _, ok := ln.(*net.TCPListener); ok { - conn = tls.Server(conn, tlsConfig) - } - server.waitCloseConn.Add(1) - go func() { - server.acceptClient(conn, handler) - server.waitCloseConn.Done() - }() - } -} - -func (server *ConiksServer) acceptClient(conn net.Conn, handler func(req *protocol.Request) *protocol.Response) { - defer conn.Close() - conn.SetDeadline(time.Now().Add(5 * time.Second)) - - var buf bytes.Buffer - var response *protocol.Response - if _, err := io.CopyN(&buf, conn, 8192); err != nil && err != io.EOF { - server.logger.Error(err.Error(), - "address", conn.RemoteAddr().String()) - return - } - - // unmarshalling - req, err := UnmarshalRequest(buf.Bytes()) - if err != nil { - response = malformedClientMsg(err) - } else { - response = handler(req) - if response.Error != protocol.ReqSuccess { - server.logger.Warn(response.Error.Error(), - "address", conn.RemoteAddr().String()) - } - } - - // marshalling - res, e := MarshalResponse(response) - if e != nil { - panic(e) - } - _, err = conn.Write([]byte(res)) - if err != nil { - server.logger.Error(err.Error(), - "address", conn.RemoteAddr().String()) - return - } -} - -func malformedClientMsg(err error) *protocol.Response { - // check if we're just propagating a message - if err == nil { - err = protocol.ErrMalformedMessage - } - return protocol.NewErrorResponse(protocol.ErrMalformedMessage) -} - -// handleOps validates the request message and then pass it to -// appropriate operation handler according to the request type. -func (server *ConiksServer) handleOps(req *protocol.Request) *protocol.Response { - switch req.Type { - case protocol.RegistrationType: - if msg, ok := req.Request.(*protocol.RegistrationRequest); ok { - return server.dir.Register(msg) - } - case protocol.KeyLookupType: - if msg, ok := req.Request.(*protocol.KeyLookupRequest); ok { - return server.dir.KeyLookup(msg) - } - case protocol.KeyLookupInEpochType: - if msg, ok := req.Request.(*protocol.KeyLookupInEpochRequest); ok { - return server.dir.KeyLookupInEpoch(msg) - } - case protocol.MonitoringType: - if msg, ok := req.Request.(*protocol.MonitoringRequest); ok { - return server.dir.Monitor(msg) - } - } - return protocol.NewErrorResponse(protocol.ErrMalformedMessage) -} - -func (server *ConiksServer) makeHandler(acceptableTypes map[int]bool) func(req *protocol.Request) *protocol.Response { - return func(req *protocol.Request) *protocol.Response { - if !acceptableTypes[req.Type] { - server.logger.Error("Unacceptable message type", - "request type", req.Type) - return malformedClientMsg(protocol.ErrMalformedMessage) - } - - switch req.Type { - case protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: - server.RLock() - default: - server.Lock() - } - response := server.handleOps(req) - switch req.Type { - case protocol.KeyLookupType, protocol.KeyLookupInEpochType, protocol.MonitoringType: - server.RUnlock() - default: - server.Unlock() - } - - return response - } -} diff --git a/coniksserver/server.go b/coniksserver/server.go deleted file mode 100644 index cc39632..0000000 --- a/coniksserver/server.go +++ /dev/null @@ -1,290 +0,0 @@ -package coniksserver - -import ( - "crypto/tls" - "fmt" - "io/ioutil" - "net" - "net/url" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/BurntSushi/toml" - "github.com/coniks-sys/coniks-go/crypto/sign" - "github.com/coniks-sys/coniks-go/crypto/vrf" - "github.com/coniks-sys/coniks-go/protocol" - "github.com/coniks-sys/coniks-go/protocol/directory" - "github.com/coniks-sys/coniks-go/utils" -) - -// A ServerConfig contains configuration values -// which are read at initialization time from -// a TOML format configuration file. -type ServerConfig struct { - // LoadedHistoryLength is the maximum number of - // snapshots kept in memory. - LoadedHistoryLength uint64 `toml:"loaded_history_length"` - // Policies contains the server's CONIKS policies configuration. - Policies *ServerPolicies `toml:"policies"` - // Addresses contains the server's connections configuration. - Addresses []*Address `toml:"addresses"` - Logger *utils.LoggerConfig `toml:"logger"` - configFilePath string -} - -// An Address describes a server's connection. -// It makes the server connections configurable -// so that a key server implementation can easily -// be run by a first-party identity provider or -// a third-party communication service. -// It supports two types of connections: a TCP connection ("tcp") -// and a Unix socket connection ("unix"). -// -// Allowing registration has to be specified explicitly for each connection. -// Other types of requests are allowed by default. -// One can think of a registration as a "write" to a key directory, -// while the other request types are "reads". -// So, by default, addresses are "read-only". -// -// Additionally, TCP connections must use TLS for added security, -// and each is required to specify a TLS certificate and corresponding -// private key. -type Address struct { - // Address is formatted as a url: scheme://address. - Address string `toml:"address"` - AllowRegistration bool `toml:"allow_registration,omitempty"` - // TLSCertPath is a path to the server's TLS Certificate, - // which has to be set if the connection is TCP. - TLSCertPath string `toml:"cert,omitempty"` - // TLSKeyPath is a path to the server's TLS private key, - // which has to be set if the connection is TCP. - TLSKeyPath string `toml:"key,omitempty"` -} - -// ServerPolicies contains a server's CONIKS policies configuration -// including paths to the VRF private key, the signing private -// key and the epoch deadline value in seconds. -type ServerPolicies struct { - EpochDeadline protocol.Timestamp `toml:"epoch_deadline"` - VRFKeyPath string `toml:"vrf_key_path"` - SignKeyPath string `toml:"sign_key_path"` // it should be a part of policies, see #47 - vrfKey vrf.PrivateKey - signKey sign.PrivateKey -} - -// A ConiksServer represents a CONIKS key server. -// It wraps a ConiksDirectory with a network layer which -// handles requests/responses and their encoding/decoding. -// A ConiksServer also supports concurrent handling of requests and -// a mechanism to update the underlying ConiksDirectory automatically -// at regular time intervals. -type ConiksServer struct { - logger *utils.Logger - - sync.RWMutex - dir *directory.ConiksDirectory - - stop chan struct{} - waitStop sync.WaitGroup - waitCloseConn sync.WaitGroup - - configFilePath string - reloadChan chan os.Signal - epochTimer *time.Timer -} - -// LoadServerConfig loads the ServerConfig for the server from the -// corresponding config file. It reads the siging key pair and the VRF key -// pair into the ServerConfig instance and updates the path of -// TLS certificate files of each Address to absolute path. -func LoadServerConfig(file string) (*ServerConfig, error) { - var conf ServerConfig - if _, err := toml.DecodeFile(file, &conf); err != nil { - return nil, fmt.Errorf("Failed to load config: %v", err) - } - - // load signing key - signPath := utils.ResolvePath(conf.Policies.SignKeyPath, file) - signKey, err := ioutil.ReadFile(signPath) - if err != nil { - return nil, fmt.Errorf("Cannot read signing key: %v", err) - } - if len(signKey) != sign.PrivateKeySize { - return nil, fmt.Errorf("Signing key must be 64 bytes (got %d)", len(signKey)) - } - - // load VRF key - vrfPath := utils.ResolvePath(conf.Policies.VRFKeyPath, file) - vrfKey, err := ioutil.ReadFile(vrfPath) - if err != nil { - return nil, fmt.Errorf("Cannot read VRF key: %v", err) - } - if len(vrfKey) != vrf.PrivateKeySize { - return nil, fmt.Errorf("VRF key must be 64 bytes (got %d)", len(vrfKey)) - } - - conf.configFilePath = file - conf.Policies.vrfKey = vrfKey - conf.Policies.signKey = signKey - // also update path for TLS cert files - for _, addr := range conf.Addresses { - addr.TLSCertPath = utils.ResolvePath(addr.TLSCertPath, file) - addr.TLSKeyPath = utils.ResolvePath(addr.TLSKeyPath, file) - } - // logger config - conf.Logger.Path = utils.ResolvePath(conf.Logger.Path, file) - - return &conf, nil -} - -// NewConiksServer creates a new reference implementation of -// a CONIKS key server. -func NewConiksServer(conf *ServerConfig) *ConiksServer { - // create server instance - server := new(ConiksServer) - server.logger = utils.NewLogger(conf.Logger) - server.dir = directory.New( - conf.Policies.EpochDeadline, - conf.Policies.vrfKey, - conf.Policies.signKey, - conf.LoadedHistoryLength, - true) - server.stop = make(chan struct{}) - server.configFilePath = conf.configFilePath - server.reloadChan = make(chan os.Signal, 1) - signal.Notify(server.reloadChan, syscall.SIGUSR2) - server.epochTimer = time.NewTimer(time.Duration(conf.Policies.EpochDeadline) * time.Second) - - return server -} - -// Run implements the main functionality of the key server. -// It listens for all declared connections with corresponding -// permissions. -// It also supports hot-reloading the configuration by listening for -// SIGUSR2 signal. -func (server *ConiksServer) Run(addrs []*Address) { - server.waitStop.Add(1) - go func() { - server.epochUpdate() - server.waitStop.Done() - }() - - hasRegistrationPerm := false - for i := 0; i < len(addrs); i++ { - addr := addrs[i] - hasRegistrationPerm = hasRegistrationPerm || addr.AllowRegistration - ln, tlsConfig, perms := resolveAndListen(addr) - server.waitStop.Add(1) - go func() { - verb := "Listening" - if addr.AllowRegistration { - verb = "Accepting registrations" - } - server.logger.Info(verb, "address", addr.Address) - server.handleRequests(ln, tlsConfig, server.makeHandler(perms)) - server.waitStop.Done() - }() - } - - if !hasRegistrationPerm { - server.logger.Warn("None of the addresses permit registration") - } - - server.waitStop.Add(1) - go func() { - server.updatePolicies() - server.waitStop.Done() - }() -} - -func (server *ConiksServer) epochUpdate() { - for { - select { - case <-server.stop: - return - case <-server.epochTimer.C: - server.Lock() - server.dir.Update() - server.epochTimer.Reset(time.Duration(server.dir.EpochDeadline()) * time.Second) - server.Unlock() - } - } -} - -func (server *ConiksServer) updatePolicies() { - for { - select { - case <-server.stop: - return - case <-server.reloadChan: - // read server policies from config file - conf, err := LoadServerConfig(server.configFilePath) - if err != nil { - // error occured while reading server config - // simply abort the reloading policies process - server.logger.Error(err.Error()) - return - } - server.Lock() - server.dir.SetPolicies(conf.Policies.EpochDeadline) - server.Unlock() - server.logger.Info("Policies reloaded!") - } - } -} - -func resolveAndListen(addr *Address) (ln net.Listener, - tlsConfig *tls.Config, - perms map[int]bool) { - perms = make(map[int]bool) - perms[protocol.KeyLookupType] = true - perms[protocol.KeyLookupInEpochType] = true - perms[protocol.MonitoringType] = true - perms[protocol.RegistrationType] = addr.AllowRegistration - - u, err := url.Parse(addr.Address) - if err != nil { - panic(err) - } - switch u.Scheme { - case "tcp": - // force to use TLS - cer, err := tls.LoadX509KeyPair(addr.TLSCertPath, addr.TLSKeyPath) - if err != nil { - panic(err) - } - tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} - tcpaddr, err := net.ResolveTCPAddr(u.Scheme, u.Host) - if err != nil { - panic(err) - } - ln, err = net.ListenTCP(u.Scheme, tcpaddr) - if err != nil { - panic(err) - } - return - case "unix": - unixaddr, err := net.ResolveUnixAddr(u.Scheme, u.Path) - if err != nil { - panic(err) - } - ln, err = net.ListenUnix(u.Scheme, unixaddr) - if err != nil { - panic(err) - } - return - default: - panic("Unknown network type") - } -} - -// Shutdown closes all of the server's connections and shuts down the server. -func (server *ConiksServer) Shutdown() error { - close(server.stop) - server.waitStop.Wait() - return nil -}