From 153edc2810b1bf826cf4c74a28a09bb83e591962 Mon Sep 17 00:00:00 2001 From: Marcela Melara Date: Thu, 21 Sep 2017 10:11:06 -0400 Subject: [PATCH] Implement auditor-server protocol (#182) * Part of #151 --- protocol/auditlog.go | 132 +++++++++++++---------------- protocol/auditlog_test.go | 63 ++++++++------ protocol/auditor.go | 7 +- protocol/auditor_test.go | 117 +++++++++++++++++++++++++ protocol/consistencychecks.go | 2 +- protocol/consistencychecks_test.go | 21 ----- protocol/directory.go | 40 +++++++++ protocol/directory_test.go | 123 +++++++++++++++++++++++++++ protocol/message.go | 19 ++++- protocol/str_test.go | 73 ++++++++++++++++ protocol/testutil.go | 4 +- 11 files changed, 472 insertions(+), 129 deletions(-) diff --git a/protocol/auditlog.go b/protocol/auditlog.go index b7454d0..bb78b0e 100644 --- a/protocol/auditlog.go +++ b/protocol/auditlog.go @@ -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) @@ -28,16 +38,6 @@ 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) { @@ -45,6 +45,15 @@ func (h *directoryHistory) updateVerifiedSTR(newVerified *DirSTR) { 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. @@ -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 @@ -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 } @@ -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 } @@ -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). diff --git a/protocol/auditlog_test.go b/protocol/auditlog_test.go index 6695b1d..724ff4e 100644 --- a/protocol/auditlog_test.go +++ b/protocol/auditlog_test.go @@ -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") } } @@ -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()) } } @@ -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) @@ -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") } diff --git a/protocol/auditor.go b/protocol/auditor.go index 4d10dde..92d16ee 100644 --- a/protocol/auditor.go +++ b/protocol/auditor.go @@ -51,6 +51,7 @@ func (a *AudState) compareWithVerified(str *DirSTR) error { if reflect.DeepEqual(a.verifiedSTR, str) { return nil } + return CheckBadSTR } @@ -105,7 +106,7 @@ func (a *AudState) checkSTRAgainstVerified(str *DirSTR) error { return CheckBadSTR } - return nil + return CheckPassed } // verifySTRRange checks the consistency of a range @@ -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 } @@ -157,7 +158,7 @@ func (a *AudState) AuditDirectory(strs []*DirSTR) error { } } - return nil + return CheckPassed } // ComputeDirectoryIdentity returns the hash of diff --git a/protocol/auditor_test.go b/protocol/auditor_test.go index 8697335..a64c6ca 100644 --- a/protocol/auditor_test.go +++ b/protocol/auditor_test.go @@ -39,3 +39,120 @@ func TestComputeDirectoryIdentity(t *testing.T) { }) } } + +func TestAuditBadSTRSignature(t *testing.T) { + // create basic test directory and audit log with 4 STRs + d, aud, hist := NewTestAuditLog(t, 3) + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + + // update the directory a few more times and then try + // to update + d.Update() + + h, _ := aud.get(dirInitHash) + + // modify the latest STR so that the consistency check fails + str := d.LatestSTR() + str2 := *str.SignedTreeRoot + str2.Signature = append([]byte{}, str.Signature...) + str2.Signature[0]++ + str.SignedTreeRoot = &str2 + + // try to audit a new STR with a bad signature: + // case signature verification failure in verifySTRConsistency() + resp, _ := NewSTRHistoryRange([]*DirSTR{str}) + err := h.Audit(resp) + if err != CheckBadSignature { + t.Error("Expect", CheckBadSignature, "got", err) + } +} + +// used to be TestVerifyWithError in consistencychecks_test.go +func TestAuditBadSameEpoch(t *testing.T) { + d, pk := NewTestDirectory(t, true) + str := d.LatestSTR() + + // modify the pinning STR so that the consistency check should fail. + str2 := *str.SignedTreeRoot + str2.Signature = append([]byte{}, str.Signature...) + str2.Signature[0]++ + str.SignedTreeRoot = &str2 + + cc := NewCC(str, true, pk) + + // try to audit a diverging STR for the same epoch + // case compareWithVerified() == false in checkAgainstVerified() + err := cc.AuditDirectory([]*DirSTR{d.LatestSTR()}) + if err != CheckBadSTR { + t.Error("Expect", CheckBadSTR, "got", err) + } +} + +func TestAuditBadNewSTREpoch(t *testing.T) { + // create basic test directory and audit log with 4 STRs + d, aud, hist := NewTestAuditLog(t, 3) + + // 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() + + // try to audit only STR epoch 4: + // case str.Epoch > verifiedSTR.Epoch+1 in checkAgainstVerifiedSTR() + resp, _ := NewSTRHistoryRange([]*DirSTR{d.LatestSTR()}) + err := h.Audit(resp) + if err != CheckBadSTR { + t.Error("str.Epoch > verified.Epoch+1 - Expect", CheckBadSTR, "got", err) + } + + // try to re-audit only STR epoch 2: + // case str.Epoch < verifiedSTR.Epoch in checkAgainstVerifiedSTR() + resp, _ = d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(2), + EndEpoch: uint64(2)}) + + err = h.Audit(resp) + if err != CheckBadSTR { + t.Error("str.Epoch < verified.Epoch - Expect", CheckBadSTR, "got", err) + } +} + +func TestAuditMalformedSTRRange(t *testing.T) { + // create basic test directory and audit log with 4 STR + d, aud, hist := NewTestAuditLog(t, 3) + + // now update the directory 4 times and get a range + for i := 0; i < 4; i++ { + d.Update() + } + + resp, err := d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(4), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + + if err != ReqSuccess { + t.Fatalf("Error occurred getting the latest STR from the directory: %s", err.Error()) + } + + strs := resp.DirectoryResponse.(*STRHistoryRange) + + // make a malformed range + strs.STR[2] = nil + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + // try to audit a malformed STR range + // case str[i] == nil in verifySTRRange() loop + err1 := h.AuditDirectory(strs.STR) + if err1 != ErrMalformedDirectoryMessage { + t.Error("Expect", ErrMalformedDirectoryMessage, "got", err1) + } +} diff --git a/protocol/consistencychecks.go b/protocol/consistencychecks.go index b0ee106..25ae0bb 100644 --- a/protocol/consistencychecks.go +++ b/protocol/consistencychecks.go @@ -133,7 +133,7 @@ func (cc *ConsistencyChecks) updateSTR(requestType int, msg *Response) error { // The initial STR is pinned in the client // so cc.verifiedSTR should never be nil // FIXME: use STR slice from Response msg - if err := cc.AuditDirectory([]*DirSTR{str}); err != nil { + if err := cc.AuditDirectory([]*DirSTR{str}); err != CheckPassed { return err } diff --git a/protocol/consistencychecks_test.go b/protocol/consistencychecks_test.go index 81ac271..4f5f42b 100644 --- a/protocol/consistencychecks_test.go +++ b/protocol/consistencychecks_test.go @@ -30,27 +30,6 @@ func lookupAndVerify(d *ConiksDirectory, cc *ConsistencyChecks, return err, cc.HandleResponse(KeyLookupType, res, name, key) } -func TestVerifyWithError(t *testing.T) { - d, pk := NewTestDirectory(t, true) - str := d.LatestSTR() - - // modify the pinning STR so that the consistency check should fail. - str2 := *str.SignedTreeRoot - str2.Signature = append([]byte{}, str.Signature...) - str2.Signature[0]++ - str.SignedTreeRoot = &str2 - - cc := NewCC(str, true, pk) - - e1, e2 := registerAndVerify(d, cc, alice, key) - if e1 != ReqSuccess { - t.Error("Expect", ReqSuccess, "got", e1) - } - if e2 != CheckBadSTR { - t.Error("Expect", CheckBadSTR, "got", e2) - } -} - func TestMalformedClientMessage(t *testing.T) { d, pk := NewTestDirectory(t, true) cc := NewCC(d.LatestSTR(), true, pk) diff --git a/protocol/directory.go b/protocol/directory.go index 4f82459..34ac607 100644 --- a/protocol/directory.go +++ b/protocol/directory.go @@ -321,3 +321,43 @@ func (d *ConiksDirectory) Monitor(req *MonitoringRequest) ( return NewMonitoringProof(aps, strs) } + +// GetSTRHistory gets the directory snapshots for the epoch range +// indicated in the STRHistoryRequest req received from a CONIKS auditor. +// The response (which also includes the error code) is supposed to +// be sent back to the auditor. The returned error is used by the key +// server for logging purposes. +// +// A request with a start epoch greater than the +// latest epoch of this directory, or a start epoch greater than the +// end epoch is considered malformed, and causes +// GetSTRHistory() to return a +// message.NewErrorResponse(ErrMalformedAuditorMessage) tuple. +// GetSTRHistory() returns a message.NewSTRHistoryRange(strs) tuple. +// strs is a list of STRs for +// the epoch range [startEpoch, endEpoch], where startEpoch +// and endEpoch are the epoch range endpoints indicated in the client's +// request. If req.endEpoch is greater than d.LatestSTR().Epoch, +// the end of the range will be set to d.LatestSTR().Epoch. +func (d *ConiksDirectory) GetSTRHistory(req *STRHistoryRequest) (*Response, + ErrorCode) { + // make sure the request is well-formed + if req.StartEpoch > d.LatestSTR().Epoch || + req.EndEpoch < req.StartEpoch { + return NewErrorResponse(ErrMalformedAuditorMessage), + ErrMalformedAuditorMessage + } + + endEp := req.EndEpoch + if req.EndEpoch > d.LatestSTR().Epoch { + endEp = d.LatestSTR().Epoch + } + + var strs []*DirSTR + for ep := req.StartEpoch; ep <= endEp; ep++ { + str := NewDirSTR(d.pad.GetSTR(ep)) + strs = append(strs, str) + } + + return NewSTRHistoryRange(strs) +} diff --git a/protocol/directory_test.go b/protocol/directory_test.go index 05f098d..7730de9 100644 --- a/protocol/directory_test.go +++ b/protocol/directory_test.go @@ -279,3 +279,126 @@ func TestPoliciesChanges(t *testing.T) { t.Fatal("Maybe the STR's policies were malformed") } } + +func TestSTRHistoryRequestLatest(t *testing.T) { + // create basic test directory and audit log with 1 STR + d, aud, hist := NewTestAuditLog(t, 0) + + d.Update() + resp, err := d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(d.LatestSTR().Epoch), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + + if err != ReqSuccess { + t.Fatalf("Error occurred getting the latest STR from the directory: %s", err.Error()) + } + + str := resp.DirectoryResponse.(*STRHistoryRange) + if len(str.STR) != 1 { + t.Fatalf("Expected 1 STR from directory, got %d", len(str.STR)) + } + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + err1 := h.Audit(resp) + if err1 != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err1.Error()) + } +} + +func TestSTRHistoryRequestRangeLatest(t *testing.T) { + // create basic test directory and audit log with 4 STR + d, aud, hist := NewTestAuditLog(t, 3) + + // now update the directory 4 times and get a range + for i := 0; i < 4; i++ { + d.Update() + } + + resp, err := d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(4), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + + if err != ReqSuccess { + t.Fatalf("Error occurred getting the latest STR from the directory: %s", err.Error()) + } + + strs := resp.DirectoryResponse.(*STRHistoryRange) + if len(strs.STR) != 4 { + t.Fatalf("Expect 4 STRs from directory, got %d", len(strs.STR)) + } + + if strs.STR[3].Epoch != 7 { + t.Fatalf("Expect latest epoch of 7, got %d", strs.STR[3].Epoch) + } + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + err1 := h.Audit(resp) + if err1 != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err1.Error()) + } +} + +func TestSTRHistoryRequestInEpoch(t *testing.T) { + // create basic test directory and audit log with 4 STR + d, aud, hist := NewTestAuditLog(t, 3) + + // now update the directory 4 times and get a range + for i := 0; i < 4; i++ { + d.Update() + } + + resp, err := d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(4), + EndEpoch: uint64(5)}) + + if err != ReqSuccess { + t.Fatalf("Error occurred getting the latest STR from the directory: %s", err.Error()) + } + + 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 != 4 || strs.STR[1].Epoch != 5 { + t.Fatalf("Expect latest epoch of 5, got %d", strs.STR[1].Epoch) + } + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + err1 := h.Audit(resp) + if err1 != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err1.Error()) + } +} + +func TestSTRHistoryRequestBadRange(t *testing.T) { + // create basic test directory + d, _ := NewTestDirectory(t, true) + + d.Update() + + _, err := d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(4), + EndEpoch: uint64(2)}) + + if err != ErrMalformedAuditorMessage { + t.Fatal("Expect ErrMalformedAuditorMessage for bad STR history end epoch") + } + + _, err = d.GetSTRHistory(&STRHistoryRequest{ + StartEpoch: uint64(6), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + + if err != ErrMalformedAuditorMessage { + t.Fatal("Expect ErrMalformedAuditorMessage for out-of-bounds STR history") + } +} diff --git a/protocol/message.go b/protocol/message.go index e8e3d55..91b5287 100644 --- a/protocol/message.go +++ b/protocol/message.go @@ -16,6 +16,7 @@ const ( KeyLookupInEpochType MonitoringType AuditType + STRType ) // A Request message defines the data a CONIKS client must send to a CONIKS @@ -108,6 +109,19 @@ type AuditingRequest struct { EndEpoch uint64 } +// An STRHistoryRequest is a message with a StartEpoch and optional EndEpoch +// of an epoch range as two uint64's that a CONIKS auditor +// sends to a directory to retrieve a range of STRs starting at epoch +// StartEpoch. +// +// The response to a successful request is an STRHistoryRange with +// a list of STRs covering the epoch range [StartEpoch, EndEpoch], +// or [StartEpoch, d.LatestSTR().Epoch] if EndEpoch is omitted. +type STRHistoryRequest struct { + StartEpoch uint64 + EndEpoch uint64 +} + // A Response message indicates the result of a CONIKS client request // with an appropriate error code, and defines the set of cryptographic // proofs a CONIKS directory must return as part of its response. @@ -135,7 +149,8 @@ type DirectoryProof struct { // STR representing a range of the STR hash chain. If the range only // covers the latest epoch, the list only contains a single STR. // A CONIKS auditor returns this DirectoryResponse type upon an -// AudutingRequest. +// AuditingRequest from a client, and a CONIKS directory returns +// this message upon an STRHistoryRequest from an auditor. type STRHistoryRange struct { STR []*DirSTR } @@ -262,8 +277,6 @@ func (msg *Response) validate() error { } return nil case *STRHistoryRange: - // treat the STRHistoryRange as an auditor response - // bc validate is only called by a client if len(df.STR) == 0 { return ErrMalformedAuditorMessage } diff --git a/protocol/str_test.go b/protocol/str_test.go index 3ecbddd..00aad1d 100644 --- a/protocol/str_test.go +++ b/protocol/str_test.go @@ -21,3 +21,76 @@ func TestVerifyHashChain(t *testing.T) { savedSTR = str } } + +func TestVerifyHashChainBadPrevSTRHash(t *testing.T) { + // create basic test directory and audit log with 4 STRs + d, aud, hist := NewTestAuditLog(t, 3) + + d.Update() + + // modify the latest STR so that the consistency check fails + str := d.LatestSTR() + str2 := *str.SignedTreeRoot + str2.PreviousSTRHash = append([]byte{}, str.PreviousSTRHash...) + str2.PreviousSTRHash[0]++ + str.SignedTreeRoot = &str2 + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + // try to verify a new STR with a bad previous STR hash: + // case hash(verifiedSTR.Signature) != str.PreviousSTRHash in + // str.VerifyHashChain() + if str.VerifyHashChain(h.VerifiedSTR()) { + t.Fatal("Expect hash chain verification to fail with bad previos STR hash") + } +} + +func TestVerifyHashChainBadPrevEpoch(t *testing.T) { + // create basic test directory and audit log with 4 STRs + d, aud, hist := NewTestAuditLog(t, 3) + + d.Update() + + // modify the latest STR so that the consistency check fails + str := d.LatestSTR() + str2 := *str.SignedTreeRoot + str2.PreviousEpoch++ + str.SignedTreeRoot = &str2 + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + // try to verify a new STR with a bad previous STR hash: + // case str.PrevousEpoch != verifiedSTR.Epoch in + // str.VerifyHashChain() + if str.VerifyHashChain(h.VerifiedSTR()) { + t.Fatal("Expect hash chain verification to fail with bad previos epoch") + } +} + +func TestVerifyHashChainBadCurEpoch(t *testing.T) { + // create basic test directory and audit log with 4 STRs + d, aud, hist := NewTestAuditLog(t, 3) + + d.Update() + + // modify the latest STR so that the consistency check fails + str := d.LatestSTR() + str2 := *str.SignedTreeRoot + str2.Epoch++ + str.SignedTreeRoot = &str2 + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + h, _ := aud.get(dirInitHash) + + // try to verify a new STR with a bad previous STR hash: + // case str.Epoch != verifiedSTR.Epoch+1 in + // str.VerifyHashChain() + if str.VerifyHashChain(h.VerifiedSTR()) { + t.Fatal("Expect hash chain verification to fail with bad previos epoch") + } +} diff --git a/protocol/testutil.go b/protocol/testutil.go index f460222..02c493c 100644 --- a/protocol/testutil.go +++ b/protocol/testutil.go @@ -48,9 +48,9 @@ func NewTestAuditLog(t *testing.T, numEpochs int) (*ConiksDirectory, ConiksAudit // always include the actual latest STR hist = append(hist, d.LatestSTR()) - err := aud.Insert("test-server", pk, hist) + err := aud.InitHistory("test-server", pk, hist) if err != nil { - t.Fatalf("Error inserting a new history with %d STRs", numEpochs+1) + t.Fatalf("Error initializing a new history with %d STRs", numEpochs+1) } return d, aud, hist