diff --git a/application/auditor/config.go b/application/auditor/config.go new file mode 100644 index 0000000..e1d66cb --- /dev/null +++ b/application/auditor/config.go @@ -0,0 +1,87 @@ +package auditor + +import ( + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/protocol" +) + +// directoryConfig contains the auditor's configuration needed to send a +// request to a CONIKS server: the path to the server's signing public-key +// file and the actual public-key parsed from that file; the path to +// the server's initial STR file and the actual STR parsed from that file; +// the server's address for receiving STR history requests. +type directoryConfig struct { + SignPubkeyPath string `toml:"sign_pubkey_path"` + SigningPubKey sign.PublicKey + + InitSTRPath string `toml:"init_str_path"` + InitSTR *protocol.DirSTR + + Address string `toml:"address"` +} + +// Config maintains the auditor's configurations for all CONIKS +// directories it tracks. +type Config struct { + TrackedDirs []*directoryConfig + // TODO: Add server-side auditor config +} + +var _ application.AppConfig = (*Config)(nil) + +func newDirectoryConfig(signPubkeyPath, initSTRPath, serverAddr string) *directoryConfig { + var dconf = directoryConfig{ + SignPubkeyPath: signPubkeyPath, + InitSTRPath: initSTRPath, + Address: serverAddr, + } + + return &dconf +} + +// NewConfig initializes a new auditor configuration with the given +// server signing public key path, registration address, and +// server address. +func NewConfig() *Config { + var conf = Config{ + TrackedDirs: make([]*directoryConfig, 0), + } + return &conf +} + +// AddDirectoryConfig adds the given CONIKS server settings to the +// auditor's configuration. +func (conf *Config) AddDirectoryConfig(signPubkeyPath, initSTRPath, serverAddr string) { + dconf := newDirectoryConfig(signPubkeyPath, initSTRPath, serverAddr) + conf.TrackedDirs = append(conf.TrackedDirs, dconf) +} + +// Load initializes an auditor's configuration from the given file. +// For each directory in the configuration, it reads the signing public-key file +// and initial STR file, and parses the actual key and initial STR. +func (conf *Config) Load(file string) error { + tmp, err := application.LoadConfig(file) + if err != nil { + return err + } + conf = tmp.(*Config) + + for _, dconf := range conf.TrackedDirs { + // load signing key + signPubKey, err := application.LoadSigningPubKey(dconf.SignPubkeyPath, file) + if err != nil { + return err + } + dconf.SigningPubKey = signPubKey + + // load initial STR + initSTR, err := application.LoadInitSTR(dconf.InitSTRPath, file) + if err != nil { + return err + } + dconf.InitSTR = initSTR + } + + return nil +} diff --git a/application/auditor/doc.go b/application/auditor/doc.go new file mode 100644 index 0000000..23487bc --- /dev/null +++ b/application/auditor/doc.go @@ -0,0 +1,9 @@ +/* +Package auditor implements the CONIKS auditor service +protocol. + +Note: The auditor can current only be used in +interactive test mode with a server, and does not +accept auditing requests from CONIKS clients. +*/ +package auditor diff --git a/application/config.go b/application/config.go index 398df27..43b13c5 100644 --- a/application/config.go +++ b/application/config.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/utils" ) @@ -61,3 +62,23 @@ func LoadSigningPubKey(path, file string) (sign.PublicKey, error) { } return signPubKey, nil } + +// LoadIinitSTR loads an initial STR at the given path +// specified in the given config file. +// If there is any parsing error or the STR is malformed, +// LoadInitSTR() returns an error with a nil STR. +func LoadInitSTR(path, file string) (*protocol.DirSTR, error) { + initSTRPath := utils.ResolvePath(path, file) + initSTRBytes, err := ioutil.ReadFile(initSTRPath) + if err != nil { + return nil, fmt.Errorf("Cannot read init STR: %v", err) + } + initSTR := new(protocol.DirSTR) + if err := json.Unmarshal(initSTRBytes, &initSTR); err != nil { + return nil, fmt.Errorf("Cannot parse initial STR: %v", err) + } + if initSTR.Epoch != 0 { + return nil, fmt.Errorf("Initial STR epoch must be 0 (got %d)", initSTR.Epoch) + } + return initSTR, nil +} \ No newline at end of file diff --git a/application/encoding.go b/application/encoding.go index 2f9ce85..30c48b9 100644 --- a/application/encoding.go +++ b/application/encoding.go @@ -8,6 +8,7 @@ import ( "encoding/json" "github.com/coniks-sys/coniks-go/protocol" + "github.com/coniks-sys/coniks-go/utils" ) // MarshalRequest returns a JSON encoding of the client's request. @@ -39,6 +40,8 @@ func UnmarshalRequest(msg []byte) (*protocol.Request, error) { request = new(protocol.KeyLookupInEpochRequest) case protocol.MonitoringType: request = new(protocol.MonitoringRequest) + case protocol.STRType: + request = new(protocol.STRHistoryRequest) } if err := json.Unmarshal(content, &request); err != nil { return nil, err @@ -91,7 +94,7 @@ func UnmarshalResponse(t int, msg []byte) *protocol.Response { Error: res.Error, DirectoryResponse: response, } - case protocol.STRType: + case protocol.AuditType, protocol.STRType: response := new(protocol.STRHistoryRange) if err := json.Unmarshal(res.DirectoryResponse, &response); err != nil { return &protocol.Response{ @@ -114,3 +117,30 @@ func malformedClientMsg(err error) *protocol.Response { } return protocol.NewErrorResponse(protocol.ErrMalformedMessage) } + +// CreateSTRRequestMsg returns a JSON encoding of +// a protocol.STRHistoryRequest for the given (start, end) epoch +// range. +func CreateSTRRequestMsg(start, end uint64) ([]byte, error) { + return json.Marshal(&protocol.Request{ + Type: protocol.STRType, + Request: &protocol.STRHistoryRequest{ + StartEpoch: start, + EndEpoch: end, + }, + }) +} + +// MarshalSTRToFile serializes the given STR to the given path. +func MarshalSTRToFile(str *protocol.DirSTR, path string) error { + strBytes, err := json.Marshal(str) + if err != nil { + return err + } + + if err := utils.WriteFile(path, strBytes, 0600); err != nil { + return err + } + + return nil +} diff --git a/application/encoding_test.go b/application/encoding_test.go index 70590dc..1d35a3b 100644 --- a/application/encoding_test.go +++ b/application/encoding_test.go @@ -33,15 +33,41 @@ func TestUnmarshalErrorResponse(t *testing.T) { } } -func TestUnmarshalSampleMessage(t *testing.T) { +func TestUnmarshalMalformedSTRHistoryRange(t *testing.T) { + errResponse := protocol.NewErrorResponse(protocol.ReqNameNotFound) + msg, err := json.Marshal(errResponse) + if err != nil { + t.Fatal(err) + } + res := UnmarshalResponse(protocol.STRType, msg) + if res.Error != protocol.ErrMalformedMessage { + t.Error("Expect error", protocol.ErrMalformedMessage, + "got", res.Error) + } +} + +func TestUnmarshalSampleClientMessage(t *testing.T) { + d := directory.NewTestDirectory(t) + res := d.Register(&protocol.RegistrationRequest{ + Username: "alice", + Key: []byte("key")}) + msg, _ := MarshalResponse(res) + response := UnmarshalResponse(protocol.RegistrationType, []byte(msg)) + str := response.DirectoryResponse.(*protocol.DirectoryProof).STR[0] + if !bytes.Equal(d.LatestSTR().Serialize(), str.Serialize()) { + t.Error("Cannot unmarshal Associated Data properly") + } +} + +func TestUnmarshalSampleAuditorMessage(t *testing.T) { d := directory.NewTestDirectory(t) res := d.GetSTRHistory(&protocol.STRHistoryRequest{ - StartEpoch: 0, - EndEpoch: 0}) + StartEpoch: uint64(0), + EndEpoch: uint64(1)}) msg, _ := MarshalResponse(res) response := UnmarshalResponse(protocol.STRType, []byte(msg)) str := response.DirectoryResponse.(*protocol.STRHistoryRange).STR[0] if !bytes.Equal(d.LatestSTR().Serialize(), str.Serialize()) { - t.Error("Cannot unmarshal Associate Data properly") + t.Error("Cannot unmarshal Associated Data properly") } } diff --git a/application/server/config.go b/application/server/config.go index bd91e67..0a443fe 100644 --- a/application/server/config.go +++ b/application/server/config.go @@ -21,6 +21,8 @@ type Config struct { LoadedHistoryLength uint64 `toml:"loaded_history_length"` // Policies contains the server's CONIKS policies configuration. Policies *Policies `toml:"policies"` + // Path to store the initial STR + InitSTRPath string `toml:"init_str_path"` // Addresses contains the server's connections configuration. Addresses []*Address `toml:"addresses"` // The server's epoch interval for updating the directory diff --git a/application/server/server.go b/application/server/server.go index 2996b7c..924e1ee 100644 --- a/application/server/server.go +++ b/application/server/server.go @@ -4,6 +4,7 @@ import ( "github.com/coniks-sys/coniks-go/application" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/directory" + "github.com/coniks-sys/coniks-go/utils" ) // An Address describes a server's connection. @@ -64,6 +65,11 @@ func NewConiksServer(conf *Config) *ConiksServer { epochTimer: application.NewEpochTimer(conf.EpochDeadline), } + // save the initial STR to be used for initializing auditors + initSTRPath := utils.ResolvePath(conf.InitSTRPath, + conf.ConfigFilePath) + application.MarshalSTRToFile(server.dir.LatestSTR(), initSTRPath) + return server } diff --git a/cli/coniksauditor/README.md b/cli/coniksauditor/README.md new file mode 100644 index 0000000..8aaa281 --- /dev/null +++ b/cli/coniksauditor/README.md @@ -0,0 +1,87 @@ +# CONIKS Auditor implementation in Golang +__Do not use your real public key or private key with this test auditor.__ + +## Usage + +**Note:** This auditor CLI currently only implements the CONIKS key +directory-to-auditor protocol (i.e. the auditor only retrieves and verifies +STRs from the server, it does _not_ accept auditing requests from clients). +To test the implementation, the auditor can be run with an interactive REPL. + +##### Install the test auditor +``` +⇒ go install github.com/coniks-sys/coniks-go/coniksauditor/cli +⇒ coniksauditor -h +________ _______ __ _ ___ ___ _ _______ +| || || | | || || | | || | +| || _ || |_| || || |_| || _____| +| || | | || || || _|| |_____ +| _|| |_| || _ || || |_ |_____ | +| |_ | || | | || || _ | _____| | +|_______||_______||_| |__||___||___| |_||_______| + +Usage: + coniksauditor [command] + +Available Commands: + init Creates a config file for the auditor. + test Run the interactive test auditor. + +Use "coniksauditor [command] --help" for more information about a command. +``` + +### Configure the auditor + +- Make sure you have at least one running CONIKS directory for your +auditor to track. For information on setting up a CONIKS directory, +see our [CONIKS server setup guide](https://github.com/coniks-sys/coniks-go/blob/master/coniksserver/README.md). + +- Generate the configuration file: +``` +⇒ mkdir coniks-auditor; cd coniks-auditor +⇒ coniksauditor init +``` +- Ensure the auditor has the directory's *test* public signing key. +- Edit the configuration file as needed: + - Replace the `sign_pubkey_path` with the location of the directory's public signing key. + - Replace the `init_str_path` with the location of the directory's initial signed tree root. + - Replace the `address` with the directory's public CONIKS address (for lookups, monitoring etc). +_Note: The auditor is capable of verifying multiple key directories, but +we currently only configure the test auditor with a single directory for simplcity._ + +### Run the test auditor + +``` +⇒ coniksauditor test # this will open a REPL +``` + +##### Update the auditor with the latest STR history from the given directory +``` +> update [dir] +# The auditor should display something like this if the request is successful +[+] Valid! The auditor is up-to-date on the STR history of [dir] +``` + +This command updates the auditor's STR log for the directory upon a +successful audit. + +##### Retrieve and verify a specific STR history range +``` +> getrange [dir] [start] [end] +# The auditor should display something like this if the request is successful +[+] Success! The requested STR history range for [dir] is valid +``` + +This command only performs an audit on the requested STR history range. +It does not update the auditor's STR log for the directory. + +##### Other commands + +Use `help` for more information. + +Use `exit` to close the REPL and exit the client. + +## Disclaimer +Please keep in mind that this CONIKS auditor is under active development. +The repository may contain experimental features that aren't fully tested. +We recommend using a [tagged release](https://github.com/coniks-sys/coniks-go/releases). diff --git a/cli/coniksauditor/coniksauditor.go b/cli/coniksauditor/coniksauditor.go new file mode 100644 index 0000000..9f5d1f2 --- /dev/null +++ b/cli/coniksauditor/coniksauditor.go @@ -0,0 +1,12 @@ +// Executable CONIKS auditor. See README for +// usage instructions. +package main + +import ( + "github.com/coniks-sys/coniks-go/cli" + "github.com/coniks-sys/coniks-go/cli/coniksauditor/internal/cmd" +) + +func main() { + cli.Execute(cmd.RootCmd) +} diff --git a/cli/coniksauditor/internal/cmd/init.go b/cli/coniksauditor/internal/cmd/init.go new file mode 100644 index 0000000..a0d8d6c --- /dev/null +++ b/cli/coniksauditor/internal/cmd/init.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + "os" + "path" + + "github.com/coniks-sys/coniks-go/application" + "github.com/coniks-sys/coniks-go/application/auditor" + "github.com/coniks-sys/coniks-go/cli" + "github.com/spf13/cobra" +) + +var initCmd = cli.NewInitCommand("CONIKS auditor", 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 := auditor.NewConfig() + conf.AddDirectoryConfig("../../keyserver/coniksserver/sign.pub", + "../../keyserver/coniksserver/init_str", + "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/cli/coniksauditor/internal/cmd/root.go b/cli/coniksauditor/internal/cmd/root.go new file mode 100644 index 0000000..08ccaa6 --- /dev/null +++ b/cli/coniksauditor/internal/cmd/root.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +// RootCmd represents the base "auditor" command when called without any +// subcommands (register, lookup, ...). +var RootCmd = cli.NewRootCommand("coniksauditor", + "CONIKS auditor service implementation in Go", + ` +________ _______ __ _ ___ ___ _ _______ +| || || | | || || | | || | +| || _ || |_| || || |_| || _____| +| || | | || || || _|| |_____ +| _|| |_| || _ || || |_ |_____ | +| |_ | || | | || || _ | _____| | +|_______||_______||_| |__||___||___| |_||_______| +`) diff --git a/cli/coniksauditor/internal/cmd/version.go b/cli/coniksauditor/internal/cmd/version.go new file mode 100644 index 0000000..e1c9416 --- /dev/null +++ b/cli/coniksauditor/internal/cmd/version.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/coniks-sys/coniks-go/cli" +) + +var versionCmd = cli.NewVersionCommand("coniksauditor") + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/cli/coniksserver/internal/cmd/init.go b/cli/coniksserver/internal/cmd/init.go index 59068e4..3a3e06d 100644 --- a/cli/coniksserver/internal/cmd/init.go +++ b/cli/coniksserver/internal/cmd/init.go @@ -53,7 +53,6 @@ func mkConfig(dir string) { }, }, } - logger := &application.LoggerConfig{ EnableStacktrace: true, Environment: "development",