Skip to content

Commit

Permalink
Refactor common STR verification functionality (#170)
Browse files Browse the repository at this point in the history
* Add client-auditor messages

* Add client-auditor messages

* Create audit log structure, query API finished

* Add/Update docs to include auditor, add ReqUnknownDirectory auditor error

* Use single generic verifySTRConsistency to be used by client and auditor

* Add tests for audit log, debug audit log

* Add assertions to validate auditor messages on client

* Add generic STR response handler

* Add TODO to move all generic STR auditing code to a separate module

* [WIP] Refactor generic auditing functionality

* Add client-auditor messages

* Create audit log structure, query API finished

* Add/Update docs to include auditor, add ReqUnknownDirectory auditor error

* Use single generic verifySTRConsistency to be used by client and auditor

* Add tests for audit log, debug audit log

* Add assertions to validate auditor messages on client

* Add generic STR response handler

* # This is a combination of 2 commits.
# The first commit's message is:

Add TODO to move all generic STR auditing code to a separate module

# The 2nd commit message will be skipped:

#	Use single generic verifySTRConsistency to be used by client and auditor

* Fix documentation

* Use DirSTR instead of merkletree.SignedTreeRoot in auditlog

* Remove all references to auditor-directory communication, make auditor response message generic

* STRList -> STRHistoryRange

* Fail sooner in GetObservedSTRs

* Revert changes to protocol/str.go

* Always request epoch range in AuditingRequest, fix Insert() bug

* Index audit log by hash of directory's initial STR

* Insert latest STR into snapshot map right away

* Fix go vet error

* Change audit log index from byte array to string

* Add test case for getting an STR after an audit log update

* Refactor common functions

* Create generic auditor interface

* Fix go fmt

* Fix some merging issues

* Refactor generic auditing functionality

TODO:

* Add tests

* Renaming STR verification functions, use generic auditor functionality in auditlog and consistencychecks

* h.verifiedSTR -> h.VerifiedSTR()

* Refactor common STR verification for STR ranges from server

* Fix

* Fix documentation and formatting, revert to use map for auditlog snapshots

* Minor fixes

* Minor fix

* Typo

* Part of #151
  • Loading branch information
masomel authored Sep 1, 2017
1 parent 9d5f9fd commit 9332452
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 103 deletions.
84 changes: 51 additions & 33 deletions protocol/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ import (
)

type directoryHistory struct {
*AudState
addr string
signKey sign.PublicKey
snapshots map[uint64]*DirSTR
latestSTR *DirSTR
}

// caller validates that initSTR is for epoch 0.
func newDirectoryHistory(addr string, signKey sign.PublicKey, initSTR *DirSTR) *directoryHistory {
a := NewAuditor(signKey, initSTR)
h := &directoryHistory{
AudState: a,
addr: addr,
snapshots: make(map[uint64]*DirSTR),
}
h.updateVerifiedSTR(initSTR)
return h
}

// A ConiksAuditLog maintains the histories
Expand All @@ -27,21 +38,25 @@ type directoryHistory struct {
// 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
// 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
}

// 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
// Audit checks that a directory's STR history
// is linear and updates the auditor's state
// if the checks pass.
// Audit() first checks the oldest STR in the
// STR range received in message against the h.verfiedSTR,
// 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.
func (h *directoryHistory) Audit(msg *Response) error {
// TODO: Implement as part of the auditor-server protocol
return CheckPassed
}

// NewAuditLog constructs a new ConiksAuditLog. It creates an empty
Expand Down Expand Up @@ -72,16 +87,16 @@ func (l ConiksAuditLog) get(dirInitHash [crypto.HashSizeByte]byte) (*directoryHi
// 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.
// 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,
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
Expand All @@ -100,6 +115,8 @@ 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
Expand All @@ -112,41 +129,43 @@ func (l ConiksAuditLog) Insert(addr string, signKey sign.PublicKey,

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

h.updateLatestSTR(snaps[i])
h.updateVerifiedSTR(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.
// 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
}

if err := verifySTRConsistency(h.signKey, h.latestSTR, newSTR); err != nil {
// 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
h.updateLatestSTR(newSTR)
// FIXME: use STR slice from Response msg
h.updateVerifiedSTR(newSTR)
return nil
}

Expand All @@ -170,15 +189,14 @@ func (l ConiksAuditLog) Update(dirInitHash [crypto.HashSizeByte]byte, newSTR *Di
// 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 {
if req.EndEpoch > h.VerifiedSTR().Epoch || req.StartEpoch > req.EndEpoch {
return NewErrorResponse(ErrMalformedClientMessage),
ErrMalformedClientMessage
}
Expand Down
174 changes: 174 additions & 0 deletions protocol/auditor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// This module implements a generic CONIKS auditor, i.e. the
// functionality that clients and auditors need to verify
// a server's STR history.

package protocol

import (
"fmt"
"reflect"

"github.com/coniks-sys/coniks-go/crypto"
"github.com/coniks-sys/coniks-go/crypto/sign"
)

// Auditor provides a generic interface allowing different
// auditor types to implement specific auditing functionality.
type Auditor interface {
AuditDirectory([]*DirSTR) error
}

// AudState verifies the hash chain of a specific directory.
type AudState struct {
signKey sign.PublicKey
verifiedSTR *DirSTR
}

var _ Auditor = (*AudState)(nil)

// NewAuditor instantiates a new auditor state from a persistance storage.
func NewAuditor(signKey sign.PublicKey, verified *DirSTR) *AudState {
a := &AudState{
signKey: signKey,
verifiedSTR: verified,
}
return a
}

// VerifiedSTR returns the newly verified STR.
func (a *AudState) VerifiedSTR() *DirSTR {
return a.verifiedSTR
}

// Update updates the auditor's verifiedSTR to newSTR
func (a *AudState) Update(newSTR *DirSTR) {
a.verifiedSTR = newSTR
}

// compareWithVerified checks whether the received STR is the same as
// the verified STR in the AudState using reflect.DeepEqual().
func (a *AudState) compareWithVerified(str *DirSTR) error {
if reflect.DeepEqual(a.verifiedSTR, str) {
return nil
}
return CheckBadSTR
}

// verifySTRConsistency checks the consistency between 2 snapshots.
// It uses the signing key signKey to verify the STR's signature.
// The signKey param either comes from a client's
// pinned signing key in its consistency state,
// or an auditor's pinned signing key in its history.
func (a *AudState) verifySTRConsistency(prevSTR, str *DirSTR) error {
// verify STR's signature
if !a.signKey.Verify(str.Serialize(), str.Signature) {
return CheckBadSignature
}
if str.VerifyHashChain(prevSTR) {
return nil
}

// TODO: verify the directory's policies as well. See #115
return CheckBadSTR
}

// checkSTRAgainstVerified checks an STR str against the a.verifiedSTR.
// If str's Epoch is the same as the verified, checkSTRAgainstVerified()
// compares the two STRs directly. If str is one epoch ahead of the
// a.verifiedSTR, checkSTRAgainstVerified() checks the consistency between
// the two STRs.
// checkSTRAgainstVerified() returns nil if the check passes,
// or the appropriate consistency check error if any of the checks fail,
// or str's epoch is anything other than the same or one ahead of
// a.verifiedSTR.
func (a *AudState) checkSTRAgainstVerified(str *DirSTR) error {
// 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: we are returning the error immediately
// without saving the inconsistent STR
// see: https://github.com/coniks-sys/coniks-go/pull/74#commitcomment-19804686
switch {
case str.Epoch == a.verifiedSTR.Epoch:
// Checking an STR in the same epoch
if err := a.compareWithVerified(str); err != nil {
return err
}
case str.Epoch == a.verifiedSTR.Epoch+1:
// Otherwise, expect that we've entered a new epoch
if err := a.verifySTRConsistency(a.verifiedSTR, str); err != nil {
return err
}
default:
return CheckBadSTR
}

return nil
}

// verifySTRRange checks the consistency of a range
// of a directory's STRs. It begins by verifying the STR consistency between
// the given prevSTR and the first STR in the given range, and
// then verifies the consistency between each subsequent STR pair.
func (a *AudState) verifySTRRange(prevSTR *DirSTR, strs []*DirSTR) error {
prev := prevSTR
for i := 0; i < len(strs); i++ {
str := strs[i]
if str == nil {
// FIXME: if this comes from the auditor, this
// should really be an ErrMalformedAuditorMessage
return ErrMalformedDirectoryMessage
}

// verify the consistency of each STR in the range
if err := a.verifySTRConsistency(prev, str); err != nil {
return err
}

prev = str
}

return nil
}

// AuditDirectory validates a range of STRs received from a CONIKS directory.
// AuditDirectory() checks the consistency of the oldest STR in the range
// against the verifiedSTR, and verifies the remaining
// range if the message contains more than one STR.
// AuditDirectory() returns the appropriate consistency check error
// if any of the checks fail, or nil if the checks pass.
func (a *AudState) AuditDirectory(strs []*DirSTR) error {
// validate strs
if len(strs) == 0 {
return ErrMalformedDirectoryMessage
}

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

// verify the entire range if we have received more than one STR
if len(strs) > 1 {
if err := a.verifySTRRange(strs[0], strs[1:]); err != nil {
return err
}
}

return nil
}

// ComputeDirectoryIdentity returns the hash of
// the directory's initial STR as a byte array.
// 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
}
File renamed without changes.
20 changes: 0 additions & 20 deletions protocol/common.go

This file was deleted.

Loading

0 comments on commit 9332452

Please sign in to comment.