Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement the auditor-server application layer #193

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
87 changes: 87 additions & 0 deletions application/auditor/config.go
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions application/auditor/doc.go
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions application/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package application

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"

"github.com/BurntSushi/toml"
"github.com/coniks-sys/coniks-go/crypto/sign"
"github.com/coniks-sys/coniks-go/protocol"
"github.com/coniks-sys/coniks-go/utils"
)

Expand All @@ -33,6 +35,26 @@ 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
}

// 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.
Expand Down
40 changes: 37 additions & 3 deletions application/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -74,8 +77,12 @@ func UnmarshalResponse(t int, msg []byte) *protocol.Response {
Error: res.Error,
}
if err := response.Validate(); err != nil {
return &protocol.Response{
Error: protocol.ErrMalformedMessage,
// we don't want to return an ErrMalformedMessage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@masomel masomel Jan 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the problem is that this function was still returning an ErrMalformedMessage even if the error is in errors. In other words, because Validate() in message.go returns msg.Error, which gives err == nil after Validate() returns, the return &protocol.Response{Error: protocol.ErrMalformedMessage } statement was always being called, even when the msg.Error was in errors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I gave it a try in https://github.com/coniks-sys/coniks-go/compare/unmarshalling. If it's ok, feel free to merge to this branch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes looks good to me. Thank you! I also like that you re-wrote the test cases. I'll merge this branch when I get a chance.

// if Error is in errors
if err == protocol.ErrMalformedMessage {
return &protocol.Response{
Error: protocol.ErrMalformedMessage,
}
}
}
return response
Expand All @@ -93,7 +100,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{
Expand All @@ -116,3 +123,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
}
49 changes: 44 additions & 5 deletions application/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,20 @@ func TestUnmarshalErrorResponse(t *testing.T) {
}
}

func TestUnmarshalMalformedErrorResponse(t *testing.T) {
func TestUnmarshalErrorSTRHistoryResponse(t *testing.T) {
errResponse := protocol.NewErrorResponse(protocol.ErrAuditLog)
msg, err := json.Marshal(errResponse)
if err != nil {
t.Fatal(err)
}
res := UnmarshalResponse(protocol.AuditType, msg)
if res.Error != protocol.ErrAuditLog {
t.Error("Expect error", protocol.ErrAuditLog,
"got", res.Error)
}
}

func TestUnmarshalMalformedDirectoryProof(t *testing.T) {
errResponse := protocol.NewErrorResponse(protocol.ReqNameNotFound)
msg, err := json.Marshal(errResponse)
if err != nil {
Expand All @@ -35,15 +48,41 @@ func TestUnmarshalMalformedErrorResponse(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")
}
}
2 changes: 2 additions & 0 deletions application/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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"`
}
Expand Down
6 changes: 6 additions & 0 deletions application/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,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.
Expand Down Expand Up @@ -68,6 +69,11 @@ func NewConiksServer(conf *Config) *ConiksServer {
epochTimer: time.NewTimer(time.Duration(conf.Policies.EpochDeadline) * time.Second),
}

// 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
}

Expand Down
87 changes: 87 additions & 0 deletions cli/coniksauditor/README.md
Original file line number Diff line number Diff line change
@@ -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).
Loading