diff --git a/protocol/auditlog.go b/protocol/auditlog.go new file mode 100644 index 0000000..a412c76 --- /dev/null +++ b/protocol/auditlog.go @@ -0,0 +1,193 @@ +// This module implements a CONIKS audit log that a CONIKS auditor +// maintains. +// An audit log is a mirror of many CONIKS key directories' STR history, +// allowing CONIKS clients to audit the CONIKS directories. + +package protocol + +import ( + "github.com/coniks-sys/coniks-go/crypto" + "github.com/coniks-sys/coniks-go/crypto/sign" +) + +type directoryHistory struct { + addr string + signKey sign.PublicKey + snapshots map[uint64]*DirSTR + latestSTR *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 + +// updateLatestSTR inserts a new STR into a directory history; +// assumes the STR has been validated by the caller +func (h *directoryHistory) updateLatestSTR(newLatest *DirSTR) { + h.snapshots[newLatest.Epoch] = newLatest + h.latestSTR = newLatest +} + +// caller validates that initSTR is for epoch 0 +func newDirectoryHistory(addr string, signKey sign.PublicKey, initSTR *DirSTR) *directoryHistory { + h := new(directoryHistory) + h.addr = addr + h.signKey = signKey + h.snapshots = make(map[uint64]*DirSTR) + h.updateLatestSTR(initSTR) + return h +} + +// NewAuditLog constructs a new ConiksAuditLog. It creates an empty +// log; the auditor will add an entry for each CONIKS directory +// the first time it observes an STR for that directory. +func NewAuditLog() ConiksAuditLog { + return make(map[[crypto.HashSizeByte]byte]*directoryHistory) +} + +// 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) { + l[dirInitHash] = dirHistory +} + +// get retrieves the directory history for the given directory identifier +// dirInitHash from the ConiksAuditLog. +// Get() also returns a boolean indicating whether the requested dirInitHash +// is present in the log. +func (l ConiksAuditLog) get(dirInitHash [crypto.HashSizeByte]byte) (*directoryHistory, bool) { + h, ok := l[dirInitHash] + return h, ok +} + +// Insert creates a new directory history for the key directory addr +// and inserts it into the audit log l. +// 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 +// a new history for a known directory, an ErrMalformedDirectoryMessage +// if oldSTRs is malformed, 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. +// 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, + snaps []*DirSTR) error { + + // make sure we're getting an initial STR at the very least + if len(snaps) < 1 || snaps[0].Epoch != 0 { + return ErrMalformedDirectoryMessage + } + + // compute the hash of the initial STR + dirInitHash := ComputeDirectoryIdentity(snaps[0]) + + // error if we want to create a new entry for a directory + // we already know + h, ok := l.get(dirInitHash) + if ok { + return ErrAuditLog + } + + // create the new directory history + h = newDirectoryHistory(addr, signKey, snaps[0]) + + // 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 := verifySTRConsistency(signKey, h.latestSTR, str); err != nil { + return err + } + + h.updateLatestSTR(snaps[i]) + } + + // Finally, add the new history to the log + l.set(dirInitHash, h) + + return nil +} + +// Update verifies the consistency of a newly observed STR newSTR for +// the directory addr, and inserts the newSTR into addr's directory history +// if the checks (i.e. STR signature and hash chain verifications) pass. +// Update() returns nil if the checks pass, and the appropriate consistency +// check error otherwise. Update() assumes that Insert() has been called for +// addr prior to its first call and thereby expects that an entry for addr +// exists in the audit log l. +// 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 + } + + if err := verifySTRConsistency(h.signKey, h.latestSTR, newSTR); err != nil { + return err + } + + // update the latest STR + h.updateLatestSTR(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). +// The response (which also includes the error code) is sent back to +// the client. The returned error is used by the auditor +// for logging purposes. +// +// A request without a directory address, with a StartEpoch or EndEpoch +// greater than the latest observed epoch of this directory, or with +// at StartEpoch > EndEpoch is considered +// malformed and causes GetObservedSTRs() to return a +// message.NewErrorResponse(ErrMalformedClientMessage) tuple. +// GetObservedSTRs() returns a message.NewSTRHistoryRange(strs) tuple. +// strs is a list of STRs for the epoch range [StartEpoch, EndEpoch]; +// if StartEpoch == EndEpoch, the list returned is of length 1. +// If the auditor doesn't have any history entries for the requested CONIKS +// directory, GetObservedSTRs() returns a +// message.NewErrorResponse(ReqUnknownDirectory) tuple. +func (l ConiksAuditLog) GetObservedSTRs(req *AuditingRequest) (*Response, + ErrorCode) { + + // make sure we have a history for the requested directory in the log + h, ok := l.get(req.DirInitSTRHash) + if !ok { + return NewErrorResponse(ReqUnknownDirectory), ReqUnknownDirectory + } + + // make sure the request is well-formed + if req.EndEpoch > h.latestSTR.Epoch || req.StartEpoch > req.EndEpoch { + return NewErrorResponse(ErrMalformedClientMessage), + ErrMalformedClientMessage + } + + var strs []*DirSTR + for ep := req.StartEpoch; ep <= req.EndEpoch; ep++ { + str := h.snapshots[ep] + strs = append(strs, str) + } + + return NewSTRHistoryRange(strs) +} diff --git a/protocol/auditlog_test.go b/protocol/auditlog_test.go new file mode 100644 index 0000000..6695b1d --- /dev/null +++ b/protocol/auditlog_test.go @@ -0,0 +1,227 @@ +package protocol + +import ( + "github.com/coniks-sys/coniks-go/crypto" + "testing" +) + +func TestInsertEmptyHistory(t *testing.T) { + // create basic test directory and audit log with 1 STR + _, _, _ = NewTestAuditLog(t, 0) +} + +func TestUpdateHistory(t *testing.T) { + // create basic test directory and audit log with 1 STR + d, aud, hist := NewTestAuditLog(t, 0) + + // update the directory so we can update the audit log + dirInitHash := ComputeDirectoryIdentity(hist[0]) + d.Update() + err := aud.Update(dirInitHash, d.LatestSTR()) + + if err != nil { + t.Fatal("Error updating the server history") + } +} + +func TestInsertPriorHistory(t *testing.T) { + // create basic test directory and audit log with 11 STRs + _, _, _ = NewTestAuditLog(t, 10) +} + +func TestInsertExistingHistory(t *testing.T) { + // create basic test directory and audit log with 1 STR + _, aud, hist := NewTestAuditLog(t, 0) + + // let's make sure that we can't re-insert a new server + // history into our log + err := aud.Insert("test-server", nil, hist) + if err != ErrAuditLog { + t.Fatal("Expected an ErrAuditLog when inserting an existing server history") + } +} + +func TestUpdateUnknownHistory(t *testing.T) { + // create basic test directory and audit log with 1 STR + d, aud, _ := 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") + } +} + +func TestUpdateBadNewSTR(t *testing.T) { + // create basic test directory and audit log with 11 STRs + d, aud, hist := NewTestAuditLog(t, 10) + + // 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() + d.Update() + + err := aud.Update(dirInitHash, d.LatestSTR()) + if err != CheckBadSTR { + t.Fatal("Expected a CheckBadSTR when attempting update a server history with a bad STR") + } +} + +func TestGetLatestObservedSTR(t *testing.T) { + // create basic test directory and audit log with 1 STR + d, aud, hist := NewTestAuditLog(t, 0) + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + + res, err := aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: uint64(d.LatestSTR().Epoch), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + if err != ReqSuccess { + t.Fatal("Unable to get latest observed STR") + } + + obs := res.DirectoryResponse.(*STRHistoryRange) + if len(obs.STR) == 0 { + t.Fatal("Expect returned STR to be not nil") + } + if obs.STR[0].Epoch != d.LatestSTR().Epoch { + t.Fatal("Unexpected epoch for returned latest STR") + } +} + +func TestGetObservedSTRInEpoch(t *testing.T) { + // create basic test directory and audit log with 11 STRs + _, aud, hist := NewTestAuditLog(t, 10) + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + + res, err := aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: uint64(6), + EndEpoch: uint64(8)}) + + if err != ReqSuccess { + t.Fatal("Unable to get latest range of STRs") + } + + obs := res.DirectoryResponse.(*STRHistoryRange) + if len(obs.STR) == 0 { + t.Fatal("Expect returned STR to be not nil") + } + if len(obs.STR) != 3 { + t.Fatal("Expect 3 returned STRs") + } + if obs.STR[0].Epoch != 6 || obs.STR[2].Epoch != 8 { + t.Fatal("Unexpected epoch for returned STRs") + } +} + +func TestGetObservedSTRMultipleEpochs(t *testing.T) { + // create basic test directory and audit log with 2 STRs + d, aud, hist := NewTestAuditLog(t, 1) + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + + // first AuditingRequest + res, err := aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: uint64(0), + EndEpoch: d.LatestSTR().Epoch}) + + if err != ReqSuccess { + t.Fatal("Unable to get latest range of STRs") + } + + obs := res.DirectoryResponse.(*STRHistoryRange) + if len(obs.STR) != 2 { + t.Fatal("Unexpected number of returned STRs") + } + if obs.STR[0].Epoch != 0 { + t.Fatal("Unexpected initial epoch for returned STR range") + } + if obs.STR[1].Epoch != d.LatestSTR().Epoch { + t.Fatal("Unexpected latest STR epoch for returned STR") + } + + // go to next epoch + d.Update() + err1 := aud.Update(dirInitHash, d.LatestSTR()) + if err1 != nil { + t.Fatal("Error occurred updating audit log after auditing request") + } + + // request the new latest STR + res, err = aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: d.LatestSTR().Epoch, + EndEpoch: d.LatestSTR().Epoch}) + + if err != ReqSuccess { + t.Fatal("Unable to get new latest STRs") + } + + obs = res.DirectoryResponse.(*STRHistoryRange) + if len(obs.STR) != 1 { + t.Fatal("Unexpected number of new latest STRs") + } + if obs.STR[0].Epoch != d.LatestSTR().Epoch { + t.Fatal("Unexpected new latest STR epoch") + } + +} + +func TestGetObservedSTRUnknown(t *testing.T) { + // create basic test directory and audit log with 11 STRs + d, aud, _ := NewTestAuditLog(t, 10) + + var unknown [crypto.HashSizeByte]byte + _, err := aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: unknown, + StartEpoch: uint64(d.LatestSTR().Epoch), + EndEpoch: uint64(d.LatestSTR().Epoch)}) + if err != ReqUnknownDirectory { + t.Fatal("Expect ReqUnknownDirectory for latest STR") + } + + _, err = aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: unknown, + StartEpoch: uint64(6), + EndEpoch: uint64(8)}) + if err != ReqUnknownDirectory { + t.Fatal("Expect ReqUnknownDirectory for older STR") + } + +} + +func TestGetObservedSTRMalformed(t *testing.T) { + // create basic test directory and audit log with 11 STRs + _, aud, hist := NewTestAuditLog(t, 10) + + // compute the hash of the initial STR for later lookups + dirInitHash := ComputeDirectoryIdentity(hist[0]) + + // also test the epoch range + _, err := aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: uint64(6), + EndEpoch: uint64(4)}) + if err != ErrMalformedClientMessage { + t.Fatal("Expect ErrMalformedClientMessage for bad end epoch") + } + _, err = aud.GetObservedSTRs(&AuditingRequest{ + DirInitSTRHash: dirInitHash, + StartEpoch: uint64(6), + EndEpoch: uint64(11)}) + if err != ErrMalformedClientMessage { + t.Fatal("Expect ErrMalformedClientMessage for out-of-bounds epoch range") + } +} diff --git a/protocol/common.go b/protocol/common.go new file mode 100644 index 0000000..01f277b --- /dev/null +++ b/protocol/common.go @@ -0,0 +1,20 @@ +package protocol + +import ( + "fmt" + + "github.com/coniks-sys/coniks-go/crypto" +) + +// ComputeDirectoryIdentity returns the hash of +// the directory's initial STR as a string. +// It panics if the STR isn't an initial STR (i.e. str.Epoch != 0). +func ComputeDirectoryIdentity(str *DirSTR) [crypto.HashSizeByte]byte { + if str.Epoch != 0 { + panic(fmt.Sprintf("[coniks] Expect epoch 0, got %x", str.Epoch)) + } + + var initSTRHash [crypto.HashSizeByte]byte + copy(initSTRHash[:], crypto.Digest(str.Signature)) + return initSTRHash +} diff --git a/protocol/common_test.go b/protocol/common_test.go new file mode 100644 index 0000000..8697335 --- /dev/null +++ b/protocol/common_test.go @@ -0,0 +1,41 @@ +package protocol + +import ( + "testing" + + "github.com/coniks-sys/coniks-go/crypto" +) + +func TestComputeDirectoryIdentity(t *testing.T) { + d, _ := NewTestDirectory(t, true) + // str0 := d.LatestSTR() + d.Update() + str1 := d.LatestSTR() + var unknown [crypto.HashSizeByte]byte + type args struct { + str *DirSTR + } + tests := []struct { + name string + args args + want [crypto.HashSizeByte]byte + }{ + // {"normal", args{str0}, ""}, + {"panic", args{str1}, unknown}, + } + for _, tt := range tests { + // FIXME: Refactor testing. See #18. + t.Run(tt.name, func(t *testing.T) { + if tt.name == "panic" { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + } + if got := ComputeDirectoryIdentity(tt.args.str); got != tt.want { + t.Errorf("ComputeDirectoryIdentity() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/protocol/consistencychecks.go b/protocol/consistencychecks.go index 856b706..87d6141 100644 --- a/protocol/consistencychecks.go +++ b/protocol/consistencychecks.go @@ -2,6 +2,7 @@ // on data received from a CONIKS directory. // These include data binding proof verification, // and non-equivocation checks. +// TODO: move all STR-verifying functionality to a separate module package protocol @@ -110,14 +111,11 @@ func (cc *ConsistencyChecks) updateSTR(requestType int, msg *Response) error { cc.SavedSTR = str return nil } - // FIXME: check whether the STR was issued on time and whatnot. - // Maybe it has something to do w/ #81 and client transitioning between epochs. - // Try to verify w/ what's been saved if err := cc.verifySTR(str); err == nil { return nil } // Otherwise, expect that we've entered a new epoch - if err := cc.verifySTRConsistency(cc.SavedSTR, str); err != nil { + if err := verifySTRConsistency(cc.signKey, cc.SavedSTR, str); err != nil { return err } @@ -132,6 +130,12 @@ func (cc *ConsistencyChecks) updateSTR(requestType int, msg *Response) error { // verifySTR checks whether the received STR is the same with // the SavedSTR using reflect.DeepEqual(). +// FIXME: check whether the STR was issued on time and whatnot. +// Maybe it has something to do w/ #81 and client transitioning between epochs. +// Try to verify w/ what's been saved +// FIXME: make this generic so the auditor can also verify the timeliness of the +// STR etc. Might make sense to separate the comparison, which is only done on the client, +// from the rest. func (cc *ConsistencyChecks) verifySTR(str *DirSTR) error { if reflect.DeepEqual(cc.SavedSTR, str) { return nil @@ -140,12 +144,13 @@ func (cc *ConsistencyChecks) verifySTR(str *DirSTR) error { } // verifySTRConsistency checks the consistency between 2 snapshots. -// It uses the pinned signing key in cc -// to verify the STR's signature and should not verify -// the hash chain using the STR stored in cc. -func (cc *ConsistencyChecks) verifySTRConsistency(savedSTR, str *DirSTR) error { +// It uses the signing key signKey to verify the STR's signature. +// The signKey param either comes from a client's +// pinned signing key, or an auditor's pinned signing key +// in its history. +func verifySTRConsistency(signKey sign.PublicKey, savedSTR, str *DirSTR) error { // verify STR's signature - if !cc.signKey.Verify(str.Serialize(), str.Signature) { + if !signKey.Verify(str.Serialize(), str.Signature) { return CheckBadSignature } if str.VerifyHashChain(savedSTR) { diff --git a/protocol/error.go b/protocol/error.go index 866d508..be16002 100644 --- a/protocol/error.go +++ b/protocol/error.go @@ -8,18 +8,23 @@ package protocol // An ErrorCode implements the built-in error interface type. type ErrorCode int -// These codes indicate the status of a client-server message exchange. +// These codes indicate the status of a client-server or client-auditor message +// exchange. // Codes prefixed by "Req" indicate different client request results. -// Codes prefixed by "Err" indicate an internal server error or a malformed +// Codes prefixed by "Err" indicate an internal server/auditor error or a malformed // message. const ( ReqSuccess ErrorCode = iota + 100 ReqNameExisted ReqNameNotFound + // auditor->client: no observed history for the requested directory + ReqUnknownDirectory ErrDirectory + ErrAuditLog ErrMalformedClientMessage ErrMalformedDirectoryMessage + ErrMalformedAuditorMessage ) // These codes indicate the result @@ -46,7 +51,9 @@ const ( var Errors = map[ErrorCode]bool{ ErrMalformedClientMessage: true, ErrDirectory: true, + ErrAuditLog: true, ErrMalformedDirectoryMessage: true, + ErrMalformedAuditorMessage: true, } var ( @@ -57,7 +64,9 @@ var ( ErrMalformedClientMessage: "[coniks] Malformed client message", ErrDirectory: "[coniks] Directory error", + ErrAuditLog: "[coniks] Audit log error", ErrMalformedDirectoryMessage: "[coniks] Malformed directory message", + ErrMalformedAuditorMessage: "[coniks] Malformed auditor message", CheckPassed: "[coniks] Consistency checks passed", CheckBadSignature: "[coniks] Directory's signature on STR or TB is invalid", diff --git a/protocol/message.go b/protocol/message.go index c666568..bd3d58c 100644 --- a/protocol/message.go +++ b/protocol/message.go @@ -4,7 +4,10 @@ package protocol -import m "github.com/coniks-sys/coniks-go/merkletree" +import ( + "github.com/coniks-sys/coniks-go/crypto" + m "github.com/coniks-sys/coniks-go/merkletree" +) // The types of requests CONIKS clients send during the CONIKS protocols. const ( @@ -12,6 +15,7 @@ const ( KeyLookupType KeyLookupInEpochType MonitoringType + AuditType ) // A Request message defines the data a CONIKS client must send to a CONIKS @@ -90,6 +94,20 @@ type MonitoringRequest struct { EndEpoch uint64 } +// An AuditingRequest is a message with a CONIKS key directory's address +// as a string, and a StartEpoch and an EndEpoch as uint64's that a CONIKS +// client sends to a CONIKS auditor to request the given directory's +// STRs for the given epoch range. To obtain a single STR, the client +// must set StartEpoch = EndEpoch in the request. +// +// The response to a successful request is an STRHistoryRange with +// a list of STRs covering the epoch range [StartEpoch, EndEpoch]. +type AuditingRequest struct { + DirInitSTRHash [crypto.HashSizeByte]byte + 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. @@ -99,7 +117,7 @@ type Response struct { } // A DirectoryResponse is a message that includes cryptographic proofs -// about the key directory that a CONIKS key directory returns +// about the key directory that a CONIKS key directory or auditor returns // to a CONIKS client. type DirectoryResponse interface{} @@ -124,8 +142,17 @@ type DirectoryProofs struct { STR []*DirSTR } +// An STRHistoryRange response includes a list of signed tree roots +// 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. +type STRHistoryRange struct { + STR []*DirSTR +} + // NewErrorResponse creates a new response message indicating the error -// that occurred while a CONIKS directory was +// that occurred while a CONIKS directory or a CONIKS auditor was // processing a client request. func NewErrorResponse(e ErrorCode) *Response { return &Response{Error: e} @@ -133,6 +160,7 @@ func NewErrorResponse(e ErrorCode) *Response { var _ DirectoryResponse = (*DirectoryProof)(nil) var _ DirectoryResponse = (*DirectoryProofs)(nil) +var _ DirectoryResponse = (*STRHistoryRange)(nil) // NewRegistrationProof creates the response message a CONIKS directory // sends to a client upon a RegistrationRequest, @@ -216,6 +244,23 @@ func NewMonitoringProof(ap []*m.AuthenticationPath, }, ReqSuccess } +// NewSTRHistoryRange creates the response message a CONIKS auditor +// sends to a client upon an AuditingRequest, +// and returns a Response containing an STRHistoryRange struct. +// auditlog.GetObservedSTRs() passes a list of one or more signed tree roots +// that the auditor observed for the requested range of epochs str. +// +// See auditlog.GetObservedSTRs() for details on the contents of the created +// STRHistoryRange. +func NewSTRHistoryRange(str []*DirSTR) (*Response, ErrorCode) { + return &Response{ + Error: ReqSuccess, + DirectoryResponse: &STRHistoryRange{ + STR: str, + }, + }, ReqSuccess +} + func (msg *Response) validate() error { if Errors[msg.Error] { return msg.Error @@ -229,6 +274,13 @@ func (msg *Response) validate() error { case *DirectoryProofs: // TODO: also do above assertions here 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 + } + return nil default: panic("[coniks] Malformed response") } diff --git a/protocol/testutil.go b/protocol/testutil.go index 4e464ba..f460222 100644 --- a/protocol/testutil.go +++ b/protocol/testutil.go @@ -14,6 +14,7 @@ import ( func NewTestDirectory(t *testing.T, useTBs bool) ( *ConiksDirectory, sign.PublicKey) { + // FIXME: NewTestDirectory should use a fixed VRF and Signing keys. vrfKey, err := vrf.GenerateKey(nil) if err != nil { t.Fatal(err) @@ -28,3 +29,29 @@ func NewTestDirectory(t *testing.T, useTBs bool) ( d := NewDirectory(1, vrfKey, signKey, 10, useTBs) return d, pk } + +// NewTestAuditLog creates a ConiksAuditLog and corresponding +// ConiksDirectory used for testing auditor-side CONIKS operations. +// The new audit log can be initialized with the number of epochs +// indicating the length of the directory history with which to +// initialize the log; if numEpochs > 0, the history contains numEpochs+1 +// STRs as it always includes the STR after the last directory update +func NewTestAuditLog(t *testing.T, numEpochs int) (*ConiksDirectory, ConiksAuditLog, []*DirSTR) { + d, pk := NewTestDirectory(t, true) + aud := NewAuditLog() + + var hist []*DirSTR + for ep := 0; ep < numEpochs; ep++ { + hist = append(hist, d.LatestSTR()) + d.Update() + } + // always include the actual latest STR + hist = append(hist, d.LatestSTR()) + + err := aud.Insert("test-server", pk, hist) + if err != nil { + t.Fatalf("Error inserting a new history with %d STRs", numEpochs+1) + } + + return d, aud, hist +}