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

Implement auditor-server protocol #182

Merged
merged 7 commits into from
Sep 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 57 additions & 75 deletions protocol/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ type directoryHistory struct {
snapshots map[uint64]*DirSTR
}

// A ConiksAuditLog maintains the histories
// of all CONIKS directories known to a CONIKS auditor,
// indexing the histories by the hash of a directory's initial
// STR (specifically, the hash of the STR's signature).
// Each history includes the directory's domain addr as a string, its
// public signing key enabling the auditor to verify the corresponding
// signed tree roots, and a list with all observed snapshots in
// chronological order.
type ConiksAuditLog map[[crypto.HashSizeByte]byte]*directoryHistory

// caller validates that initSTR is for epoch 0.
func newDirectoryHistory(addr string, signKey sign.PublicKey, initSTR *DirSTR) *directoryHistory {
a := NewAuditor(signKey, initSTR)
Expand All @@ -28,23 +38,22 @@ func newDirectoryHistory(addr string, signKey sign.PublicKey, initSTR *DirSTR) *
return h
}

// A ConiksAuditLog maintains the histories
// of all CONIKS directories known to a CONIKS auditor,
// indexing the histories by the hash of a directory's initial
// STR (specifically, the hash of the STR's signature).
// Each history includes the directory's domain addr as a string, its
// public signing key enabling the auditor to verify the corresponding
// signed tree roots, and a list with all observed snapshots in
// chronological order.
type ConiksAuditLog map[[crypto.HashSizeByte]byte]*directoryHistory

// updateVerifiedSTR inserts the latest verified STR into a directory history;
// assumes the STRs have been validated by the caller.
func (h *directoryHistory) updateVerifiedSTR(newVerified *DirSTR) {
h.Update(newVerified)
h.snapshots[newVerified.Epoch] = newVerified
}

// insertRange inserts the given range of STRs snaps
// into the directoryHistory h.
// insertRange() assumes that snaps has been audited by Audit().
func (h *directoryHistory) insertRange(snaps []*DirSTR) {
for i := 0; i < len(snaps); i++ {
h.updateVerifiedSTR(snaps[i])
}
}

// Audit checks that a directory's STR history
// is linear and updates the auditor's state
// if the checks pass.
Expand All @@ -53,10 +62,28 @@ func (h *directoryHistory) updateVerifiedSTR(newVerified *DirSTR) {
// and then verifies the remaining STRs in msg, and
// finally updates the snapshots if the checks pass.
// Audit() is called when an auditor receives new STRs
// from a directory.
// from a specific directory.
func (h *directoryHistory) Audit(msg *Response) error {
// TODO: Implement as part of the auditor-server protocol
return CheckPassed
if err := msg.validate(); err != nil {
return err
}

strs := msg.DirectoryResponse.(*STRHistoryRange)

// audit the STRs
// if strs.STR is somehow malformed or invalid (e.g. strs.STR
// contains old STRs), AuditDirectory() will detect this
// and throw and error
if err := h.AuditDirectory(strs.STR); err != CheckPassed {
return err
}

// TODO: we should be storing inconsistent STRs nonetheless
// so clients can detect inconsistencies -- or auditors
// should blow the whistle and not store the bad STRs
h.insertRange(strs.STR)

return nil
}

// NewAuditLog constructs a new ConiksAuditLog. It creates an empty
Expand All @@ -68,7 +95,8 @@ func NewAuditLog() ConiksAuditLog {

// set associates the given directoryHistory with the directory identifier
// (i.e. the hash of the initial STR) dirInitHash in the ConiksAuditLog.
func (l ConiksAuditLog) set(dirInitHash [crypto.HashSizeByte]byte, dirHistory *directoryHistory) {
func (l ConiksAuditLog) set(dirInitHash [crypto.HashSizeByte]byte,
dirHistory *directoryHistory) {
l[dirInitHash] = dirHistory
}

Expand All @@ -81,24 +109,21 @@ func (l ConiksAuditLog) get(dirInitHash [crypto.HashSizeByte]byte) (*directoryHi
return h, ok
}

// Insert creates a new directory history for the key directory addr
// InitHistory creates a new directory history for the key directory addr
// and inserts it into the audit log l.
// InitHistory() is called by an auditor when it initializes its state
// from disk (either first-time startup, or after reboot).
// The directory history is initialized with the key directory's
// signing key signKey, and a list of snapshots snaps representing the
// directory's STR history so far, in chronological order.
// Insert() returns an ErrAuditLog if the auditor attempts to create
// signing key signKey, and a list of one or more snapshots snaps
// containing the pinned initial STR as well as the saved directory's
// STR history so far, in chronological order.
// InitHistory() returns an ErrAuditLog if the auditor attempts to create
// a new history for a known directory, and nil otherwise.
// Insert() only creates the initial entry in the log for addr. Use Update()
// to insert newly observed STRs for addr in subsequent epochs.
// Insert() assumes that the caller
// has called Audit() on snaps before calling Insert().
// FIXME: pass Response message as param
// masomel: will probably want to write a more generic function
// for "catching up" on a history in case an auditor misses epochs
func (l ConiksAuditLog) Insert(addr string, signKey sign.PublicKey,
func (l ConiksAuditLog) InitHistory(addr string, signKey sign.PublicKey,
snaps []*DirSTR) error {
// make sure we're getting an initial STR at the very least
if len(snaps) < 1 || snaps[0].Epoch != 0 {
// FIXME: This should be a more generic "malformed error"
return ErrMalformedDirectoryMessage
}

Expand All @@ -115,60 +140,17 @@ func (l ConiksAuditLog) Insert(addr string, signKey sign.PublicKey,
// create the new directory history
h = newDirectoryHistory(addr, signKey, snaps[0])

// FIXME: remove this check --> caller calls Audit() before
// this function
// add each STR into the history
// start at 1 since we've inserted the initial STR above
// This loop automatically catches if snaps is malformed
// (i.e. snaps is missing an epoch between 0 and the latest given)
for i := 1; i < len(snaps); i++ {
str := snaps[i]
if str == nil {
return ErrMalformedDirectoryMessage
}

// verify the consistency of each new STR before inserting
// into the audit log
if err := h.verifySTRConsistency(h.VerifiedSTR(), str); err != nil {
return err
}

h.updateVerifiedSTR(snaps[i])
}

// TODO: re-verify all snaps although auditor should have
// already done so in the past? After all, if we have
// more than one snapshot, this means that the auditor is
// re-initializing its state from disk, and it wouldn't have
// saved those STRs if they didn't pass the Audit() checks.
h.insertRange(snaps[1:])
l.set(dirInitHash, h)

return nil
}

// Update inserts a newly observed STR newSTR into the log entry for the
// directory history given by dirInitHash (hash of direcotry's initial STR).
// Update() assumes that Insert() has been called for
// dirInitHash prior to its first call and thereby expects that an
// entry for addr exists in the audit log l, and that the caller
// has called Audit() on newSTR before calling Update().
// Update() returns ErrAuditLog if the audit log doesn't contain an
// entry for dirInitHash.
// FIXME: pass Response message as param
func (l ConiksAuditLog) Update(dirInitHash [crypto.HashSizeByte]byte, newSTR *DirSTR) error {
// error if we want to update the entry for an addr we don't know
h, ok := l.get(dirInitHash)
if !ok {
return ErrAuditLog
}

// FIXME: remove this check --> caller calls Audit() before this
// function
if err := h.verifySTRConsistency(h.VerifiedSTR(), newSTR); err != nil {
return err
}

// update the latest STR
// FIXME: use STR slice from Response msg
h.updateVerifiedSTR(newSTR)
return nil
}

// GetObservedSTRs gets a range of observed STRs for the CONIKS directory
// address indicated in the AuditingRequest req received from a
// CONIKS client, and returns a tuple of the form (response, error).
Expand Down
63 changes: 39 additions & 24 deletions protocol/auditlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ func TestUpdateHistory(t *testing.T) {
// update the directory so we can update the audit log
dirInitHash := ComputeDirectoryIdentity(hist[0])
d.Update()
err := aud.Update(dirInitHash, d.LatestSTR())
h, _ := aud.get(dirInitHash)
resp, _ := NewSTRHistoryRange([]*DirSTR{d.LatestSTR()})

err := h.Audit(resp)

if err != nil {
t.Fatal("Error updating the server history")
t.Fatal("Error auditing and updating the server history")
}
}

Expand All @@ -35,40 +38,49 @@ func TestInsertExistingHistory(t *testing.T) {

// let's make sure that we can't re-insert a new server
// history into our log
err := aud.Insert("test-server", nil, hist)
err := aud.InitHistory("test-server", nil, hist)
if err != ErrAuditLog {
t.Fatal("Expected an ErrAuditLog when inserting an existing server history")
}
}

func TestUpdateUnknownHistory(t *testing.T) {
func TestAuditLogBadEpochRange(t *testing.T) {
// create basic test directory and audit log with 1 STR
d, aud, _ := NewTestAuditLog(t, 0)
d, aud, hist := NewTestAuditLog(t, 0)

// let's make sure that we can't update a history for an unknown
// directory in our log
var unknown [crypto.HashSizeByte]byte
err := aud.Update(unknown, d.LatestSTR())
if err != ErrAuditLog {
t.Fatal("Expected an ErrAuditLog when updating an unknown server history")
d.Update()

resp, err := d.GetSTRHistory(&STRHistoryRequest{
StartEpoch: uint64(0),
EndEpoch: uint64(1)})

if err != ReqSuccess {
t.Fatalf("Error occurred while fetching STR history: %s", err.Error())
}
}

func TestUpdateBadNewSTR(t *testing.T) {
// create basic test directory and audit log with 11 STRs
d, aud, hist := NewTestAuditLog(t, 10)
strs := resp.DirectoryResponse.(*STRHistoryRange)
if len(strs.STR) != 2 {
t.Fatalf("Expect 2 STRs from directory, got %d", len(strs.STR))
}

if strs.STR[0].Epoch != 0 || strs.STR[1].Epoch != 1 {
t.Fatalf("Expect latest epoch of 1, got %d", strs.STR[1].Epoch)
}

// compute the hash of the initial STR for later lookups
dirInitHash := ComputeDirectoryIdentity(hist[0])
h, _ := aud.get(dirInitHash)

// update the directory a few more times and then try
// to update
d.Update()
d.Update()
err1 := h.Audit(resp)
if err1 != nil {
t.Fatalf("Error occurred while auditing STR history: %s", err1.Error())
}

err := aud.Update(dirInitHash, d.LatestSTR())
if err != CheckBadSTR {
t.Fatal("Expected a CheckBadSTR when attempting update a server history with a bad STR")
// now try to audit the same range again: should fail because the
// verified epoch is at 1
err1 = h.Audit(resp)
if err1 != CheckBadSTR {
t.Fatalf("Expecting CheckBadSTR, got %s", err1.Error())
}
}

Expand Down Expand Up @@ -138,7 +150,7 @@ func TestGetObservedSTRMultipleEpochs(t *testing.T) {
EndEpoch: d.LatestSTR().Epoch})

if err != ReqSuccess {
t.Fatal("Unable to get latest range of STRs")
t.Fatalf("Unable to get latest range of STRs, got %s", err.Error())
}

obs := res.DirectoryResponse.(*STRHistoryRange)
Expand All @@ -154,7 +166,10 @@ func TestGetObservedSTRMultipleEpochs(t *testing.T) {

// go to next epoch
d.Update()
err1 := aud.Update(dirInitHash, d.LatestSTR())
h, _ := aud.get(dirInitHash)
resp, _ := NewSTRHistoryRange([]*DirSTR{d.LatestSTR()})

err1 := h.Audit(resp)
if err1 != nil {
t.Fatal("Error occurred updating audit log after auditing request")
}
Expand Down
7 changes: 4 additions & 3 deletions protocol/auditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (a *AudState) compareWithVerified(str *DirSTR) error {
if reflect.DeepEqual(a.verifiedSTR, str) {
return nil
}

return CheckBadSTR
}

Expand Down Expand Up @@ -105,7 +106,7 @@ func (a *AudState) checkSTRAgainstVerified(str *DirSTR) error {
return CheckBadSTR
}

return nil
Copy link
Member

@vqhuy vqhuy Aug 21, 2017

Choose a reason for hiding this comment

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

I prefer returning nil here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean only for this function because it's only a helper function, or in all the cases in which I replaced nil with CheckPassed? I feel like it would definitely make sense to return CheckPassed from AuditDirectory() at least.

Copy link
Member

Choose a reason for hiding this comment

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

I mean only for this function ;)

return CheckPassed
}

// verifySTRRange checks the consistency of a range
Expand Down Expand Up @@ -146,7 +147,7 @@ func (a *AudState) AuditDirectory(strs []*DirSTR) error {
}

// check STR against the latest verified STR
if err := a.checkSTRAgainstVerified(strs[0]); err != nil {
if err := a.checkSTRAgainstVerified(strs[0]); err != CheckPassed {
return err
}

Expand All @@ -157,7 +158,7 @@ func (a *AudState) AuditDirectory(strs []*DirSTR) error {
}
}

return nil
return CheckPassed
}

// ComputeDirectoryIdentity returns the hash of
Expand Down
Loading