Skip to content

Commit

Permalink
Implement auditor-server protocol (#182)
Browse files Browse the repository at this point in the history
* Part of #151
  • Loading branch information
masomel authored Sep 21, 2017
1 parent f82d820 commit 153edc2
Show file tree
Hide file tree
Showing 11 changed files with 472 additions and 129 deletions.
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
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

0 comments on commit 153edc2

Please sign in to comment.