diff --git a/application/bots/twitterbot_test.go b/application/bots/twitterbot_test.go index cb591b8..7101540 100644 --- a/application/bots/twitterbot_test.go +++ b/application/bots/twitterbot_test.go @@ -23,7 +23,7 @@ func TestCannotUnmarshallRequest(t *testing.T) { func TestInvalidRequestType(t *testing.T) { username := "alice" request, _ := json.Marshal(&protocol.Request{ - Type: protocol.KeyLookupType, + Type: protocol.KeyLookupInEpochType, Request: &protocol.RegistrationRequest{ Username: username + "@twitter", Key: []byte{1, 2, 3}, diff --git a/application/encoding.go b/application/encoding.go index 937f93d..6069a4f 100644 --- a/application/encoding.go +++ b/application/encoding.go @@ -73,14 +73,15 @@ func UnmarshalResponse(t int, msg []byte) *protocol.Response { // DirectoryResponse is omitempty for the places // where Error is in Errors if res.DirectoryResponse == nil { - if !protocol.Errors[res.Error] { + response := &protocol.Response{ + Error: res.Error, + } + if err := response.Validate(); err != nil { return &protocol.Response{ Error: protocol.ErrMalformedMessage, } } - return &protocol.Response{ - Error: res.Error, - } + return response } switch t { diff --git a/application/encoding_test.go b/application/encoding_test.go index 9d936a6..e83a288 100644 --- a/application/encoding_test.go +++ b/application/encoding_test.go @@ -66,9 +66,8 @@ func TestUnmarshalSampleClientMessage(t *testing.T) { res := d.Register(&protocol.RegistrationRequest{ Username: "alice", Key: []byte("key")}) - msg, _ := MarshalResponse(res) - response := UnmarshalResponse(protocol.RegistrationType, []byte(msg)) - str := response.DirectoryResponse.(*protocol.DirectoryProof).STR[0] + response := UnmarshalResponse(protocol.RegistrationType, []byte(msg)) + str := response.DirectoryResponse.(*protocol.DirectoryProof).STR[0] if !bytes.Equal(d.LatestSTR().Serialize(), str.Serialize()) { t.Error("Cannot unmarshal Associate Data properly") } diff --git a/crypto/testutil.go b/crypto/testutil.go new file mode 100644 index 0000000..bd251a1 --- /dev/null +++ b/crypto/testutil.go @@ -0,0 +1,28 @@ +package crypto + +import ( + "bytes" + + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" +) + +// NewStaticTestVRFKey returns a static VRF private key for _tests_. +func NewStaticTestVRFKey() vrf.PrivateKey { + sk, err := vrf.GenerateKey(bytes.NewReader( + []byte("deterministic tests need 256 bit"))) + if err != nil { + panic(err) + } + return sk +} + +// NewStaticTestSigningKey returns a static private signing key for _tests_. +func NewStaticTestSigningKey() sign.PrivateKey { + sk, err := sign.GenerateKey(bytes.NewReader( + []byte("deterministic tests need 256 bit"))) + if err != nil { + panic(err) + } + return sk +} diff --git a/merkletree/merkletree_test.go b/merkletree/merkletree_test.go index a97e39a..68325f7 100644 --- a/merkletree/merkletree_test.go +++ b/merkletree/merkletree_test.go @@ -4,17 +4,11 @@ import ( "bytes" "testing" - "github.com/coniks-sys/coniks-go/crypto/vrf" "github.com/coniks-sys/coniks-go/utils" "golang.org/x/crypto/sha3" ) -var vrfPrivKey1, _ = vrf.GenerateKey(bytes.NewReader( - []byte("deterministic tests need 256 bit"))) - -var vrfPrivKey2, _ = vrf.GenerateKey(bytes.NewReader( - []byte("deterministic tests need 32 byte"))) - +// TODO: When #178 is merged, 3 tests below should be removed. func TestOneEntry(t *testing.T) { m, err := NewMerkleTree() if err != nil { @@ -26,7 +20,7 @@ func TestOneEntry(t *testing.T) { key := "key" val := []byte("value") - index := vrfPrivKey1.Compute([]byte(key)) + index := staticVRFKey.Compute([]byte(key)) if err := m.Set(index, key, val); err != nil { t.Fatal(err) } @@ -90,10 +84,10 @@ func TestTwoEntries(t *testing.T) { } key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) + index1 := staticVRFKey.Compute([]byte(key1)) val1 := []byte("value1") key2 := "key2" - index2 := vrfPrivKey1.Compute([]byte(key2)) + index2 := staticVRFKey.Compute([]byte(key2)) val2 := []byte("value2") if err := m.Set(index1, key1, val1); err != nil { @@ -130,13 +124,13 @@ func TestThreeEntries(t *testing.T) { } key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) + index1 := staticVRFKey.Compute([]byte(key1)) val1 := []byte("value1") key2 := "key2" - index2 := vrfPrivKey1.Compute([]byte(key2)) + index2 := staticVRFKey.Compute([]byte(key2)) val2 := []byte("value2") key3 := "key3" - index3 := vrfPrivKey1.Compute([]byte(key3)) + index3 := staticVRFKey.Compute([]byte(key3)) val3 := []byte("value3") if err := m.Set(index1, key1, val1); err != nil { @@ -191,13 +185,10 @@ func TestThreeEntries(t *testing.T) { } func TestInsertExistedKey(t *testing.T) { - m, err := NewMerkleTree() - if err != nil { - t.Fatal(err) - } + m := newEmptyTreeForTest(t) key1 := "key" - index1 := vrfPrivKey1.Compute([]byte(key1)) + index1 := staticVRFKey.Compute([]byte(key1)) val1 := append([]byte(nil), "value"...) if err := m.Set(index1, key1, val1); err != nil { @@ -241,10 +232,10 @@ func TestInsertExistedKey(t *testing.T) { func TestTreeClone(t *testing.T) { key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) + index1 := staticVRFKey.Compute([]byte(key1)) val1 := []byte("value1") key2 := "key2" - index2 := vrfPrivKey1.Compute([]byte(key2)) + index2 := staticVRFKey.Compute([]byte(key2)) val2 := []byte("value2") m1, err := NewMerkleTree() diff --git a/merkletree/pad_test.go b/merkletree/pad_test.go index 18467a2..d8e5b22 100644 --- a/merkletree/pad_test.go +++ b/merkletree/pad_test.go @@ -2,6 +2,7 @@ package merkletree import ( "bytes" + "strconv" "testing" "crypto/rand" @@ -10,9 +11,11 @@ import ( "io" "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" ) var signKey sign.PrivateKey +var vrfKey vrf.PrivateKey func init() { var err error @@ -20,6 +23,10 @@ func init() { if err != nil { panic(err) } + vrfKey, err = vrf.GenerateKey(nil) + if err != nil { + panic(err) + } } type TestAd struct { @@ -35,48 +42,29 @@ func (t TestAd) Serialize() []byte { // 3rd: epoch = 2 (key1, key2) // 4th: epoch = 3 (key1, key2, key3) (latest STR) func TestPADHashChain(t *testing.T) { - key1 := "key" - val1 := []byte("value") - - key2 := "key2" - val2 := []byte("value2") - - key3 := "key3" - val3 := []byte("value3") - + N := uint64(3) treeHashes := make(map[uint64][]byte) - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 10) - if err != nil { - t.Fatal(err) + afterCreate := func(pad *PAD) { + treeHashes[0] = append([]byte{}, pad.tree.hash...) } - treeHashes[0] = append([]byte{}, pad.tree.hash...) - - if err := pad.Set(key1, val1); err != nil { - t.Fatal(err) - } - pad.Update(nil) - treeHashes[1] = append([]byte{}, pad.tree.hash...) - - if err := pad.Set(key2, val2); err != nil { - t.Fatal(err) + afterInsert := func(i uint64, pad *PAD) { + pad.Update(nil) + treeHashes[i+1] = append([]byte{}, pad.tree.hash...) } - pad.Update(nil) - treeHashes[2] = append([]byte{}, pad.tree.hash...) - if err := pad.Set(key3, val3); err != nil { + pad, err := createPad(N, keyPrefix, valuePrefix, 10, afterCreate, afterInsert) + if err != nil { t.Fatal(err) } - pad.Update(nil) - treeHashes[3] = append([]byte{}, pad.tree.hash...) - for i := 0; i < 4; i++ { + for i := uint64(0); i < N; i++ { str := pad.GetSTR(uint64(i)) if str == nil { t.Fatal("Cannot get STR #", i) } if !bytes.Equal(str.TreeHash, treeHashes[uint64(i)]) { - t.Fatal("Malformed PAD Update") + t.Fatal("Malformed PAD Update:", i) } if str.Epoch != uint64(i) { @@ -93,66 +81,38 @@ func TestPADHashChain(t *testing.T) { t.Error("Got invalid STR", "want", 3, "got", str.Epoch) } - // lookup - ap, _ := pad.Lookup(key1) - if ap.Leaf.Value == nil { - t.Error("Cannot find key:", key1) - return - } - if !bytes.Equal(ap.Leaf.Value, val1) { - t.Error(key1, "value mismatch") - } - - ap, _ = pad.Lookup(key2) - if ap.Leaf.Value == nil { - t.Error("Cannot find key:", key2) - return - } - if !bytes.Equal(ap.Leaf.Value, val2) { - t.Error(key2, "value mismatch") - } - - ap, _ = pad.Lookup(key3) - if ap.Leaf.Value == nil { - t.Error("Cannot find key:", key3) - return - } - if !bytes.Equal(ap.Leaf.Value, val3) { - t.Error(key3, "value mismatch") - } - - ap, err = pad.LookupInEpoch(key2, 1) - if err != nil { - t.Error(err) - } else if ap.Leaf.Value != nil { - t.Error("Found unexpected key", key2, "in STR #", 1) - } - ap, err = pad.LookupInEpoch(key2, 2) - if err != nil { - t.Error(err) - } else if ap.Leaf.Value == nil { - t.Error("Cannot find key", key2, "in STR #", 2) - } + for i := uint64(0); i < N; i++ { + key := keyPrefix + strconv.FormatUint(i, 10) + val := append(valuePrefix, byte(i)) + ap, _ := pad.Lookup(key) + if ap.Leaf.Value == nil { + t.Fatal("Cannot find key:", key) + } + if !bytes.Equal(ap.Leaf.Value, val) { + t.Error(key, "value mismatch") + } - ap, err = pad.LookupInEpoch(key3, 2) - if err != nil { - t.Error(err) - } else if ap.Leaf.Value != nil { - t.Error("Found unexpected key", key3, "in STR #", 2) } - ap, err = pad.LookupInEpoch(key3, 3) - if err != nil { - t.Error(err) - } else if ap.Leaf.Value == nil { - t.Error("Cannot find key", key3, "in STR #", 3) + for epoch := uint64(0); epoch < N; epoch++ { + for keyNum := uint64(0); keyNum < N; keyNum++ { + key := keyPrefix + strconv.FormatUint(keyNum, 10) + ap, err := pad.LookupInEpoch(key, epoch) + if err != nil { + t.Error(err) + } else if keyNum < epoch && ap.Leaf.Value == nil { + t.Error("Cannot find key", key, "in STR #", epoch) + } else if keyNum >= epoch && ap.Leaf.Value != nil { + t.Error("Found unexpected key", key, "in STR #", epoch) + } + } } } func TestHashChainExceedsMaximumSize(t *testing.T) { var hashChainLimit uint64 = 4 - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey2, hashChainLimit) + pad, err := NewPAD(TestAd{""}, signKey, vrfKey, hashChainLimit) if err != nil { t.Fatal(err) } @@ -193,7 +153,7 @@ func TestAssocDataChange(t *testing.T) { key3 := "key3" val3 := []byte("value3") - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 10) + pad, err := NewPAD(TestAd{""}, signKey, vrfKey, 10) if err != nil { t.Fatal(err) } @@ -256,16 +216,15 @@ func TestNewPADMissingAssocData(t *testing.T) { t.Fatal("Expected NewPAD to panic if ad are missing.") } }() - if _, err := NewPAD(nil, signKey, vrfPrivKey1, 10); err != nil { + if _, err := NewPAD(nil, signKey, vrfKey, 10); err != nil { t.Fatal("Expected NewPAD to panic but got error.") } } -// TODO move the following to some (internal?) testutils package type testErrorRandReader struct{} func (er testErrorRandReader) Read([]byte) (int, error) { - return 0, errors.New("Not enough entropy!") + return 0, errors.New("not enough entropy") } func mockRandReadWithErroringReader() (orig io.Reader) { @@ -282,7 +241,7 @@ func TestNewPADErrorWhileCreatingTree(t *testing.T) { origRand := mockRandReadWithErroringReader() defer unMockRandReader(origRand) - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 3) + pad, err := NewPAD(TestAd{""}, signKey, vrfKey, 3) if err == nil || pad != nil { t.Fatal("NewPad should return an error in case the tree creation failed") } @@ -295,14 +254,11 @@ func BenchmarkCreateLargePAD(b *testing.B) { // total number of entries in tree: NumEntries := uint64(1000000) - // tree.Clone and update STR every: - noUpdate := uint64(NumEntries + 1) b.ResetTimer() // benchmark creating a large tree (don't Update tree) for n := 0; n < b.N; n++ { - _, err := createPad(NumEntries, keyPrefix, valuePrefix, snapLen, - noUpdate) + _, err := createPadSimple(NumEntries, keyPrefix, valuePrefix, snapLen) if err != nil { b.Fatal(err) } @@ -327,9 +283,8 @@ func benchPADUpdate(b *testing.B, entries uint64) { keyPrefix := "key" valuePrefix := []byte("value") snapLen := uint64(10) - noUpdate := uint64(entries + 1) // This takes a lot of time for a large number of entries: - pad, err := createPad(uint64(entries), keyPrefix, valuePrefix, snapLen, noUpdate) + pad, err := createPadSimple(uint64(entries), keyPrefix, valuePrefix, snapLen) if err != nil { b.Fatal(err) } @@ -374,9 +329,13 @@ func benchPADLookup(b *testing.B, entries uint64) { snapLen := uint64(10) keyPrefix := "key" valuePrefix := []byte("value") - updateOnce := uint64(entries - 1) - pad, err := createPad(entries, keyPrefix, valuePrefix, snapLen, - updateOnce) + updateOnce := func(iteration uint64, pad *PAD) { + if iteration == entries-1 { + pad.Update(nil) + } + } + + pad, err := createPad(entries, keyPrefix, valuePrefix, snapLen, nil, updateOnce) if err != nil { b.Fatal(err) } @@ -406,23 +365,35 @@ func benchPADLookup(b *testing.B, entries uint64) { // for i = 0,...,N // The STR will get updated every epoch defined by every multiple of // `updateEvery`. If `updateEvery > N` createPAD won't update the STR. +// `afterCreateCB` and `afterInsertCB` are 2 callbacks which would be called +// before creating the PAD and after every inserting, respectively. func createPad(N uint64, keyPrefix string, valuePrefix []byte, snapLen uint64, - updateEvery uint64) (*PAD, error) { - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, snapLen) + afterCreateCB func(pad *PAD), + afterInsertCB func(iteration uint64, pad *PAD)) (*PAD, error) { + pad, err := NewPAD(TestAd{""}, signKey, vrfKey, snapLen) if err != nil { return nil, err } + if afterCreateCB != nil { + afterCreateCB(pad) + } for i := uint64(0); i < N; i++ { - key := keyPrefix + string(i) + key := keyPrefix + strconv.FormatUint(i, 10) value := append(valuePrefix, byte(i)) if err := pad.Set(key, value); err != nil { return nil, fmt.Errorf("Couldn't set key=%s and value=%s. Error: %v", key, value, err) } - if i != 0 && (i%updateEvery == 0) { - pad.Update(nil) + if afterInsertCB != nil { + afterInsertCB(i, pad) } } return pad, nil } + +// createPadSimple calls `createPad` without any callback. +func createPadSimple(N uint64, keyPrefix string, valuePrefix []byte, + snapLen uint64) (*PAD, error) { + return createPad(N, keyPrefix, valuePrefix, snapLen, nil, nil) +} diff --git a/merkletree/proof_test.go b/merkletree/proof_test.go index e8ab46f..05c502c 100644 --- a/merkletree/proof_test.go +++ b/merkletree/proof_test.go @@ -2,192 +2,149 @@ package merkletree import ( "bytes" + "math/rand" + "strconv" "testing" + "time" "github.com/coniks-sys/coniks-go/utils" ) -func TestVerifyProof(t *testing.T) { - m, err := NewMerkleTree() - if err != nil { - t.Fatal(err) - } - - key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) - val1 := []byte("value1") - key2 := "key2" - index2 := vrfPrivKey1.Compute([]byte(key2)) - val2 := []byte("value2") - key3 := "key3" - index3 := vrfPrivKey1.Compute([]byte(key3)) - val3 := []byte("value3") - - if err := m.Set(index1, key1, val1); err != nil { - t.Fatal(err) - } - if err := m.Set(index2, key2, val2); err != nil { - t.Fatal(err) - } - if err := m.Set(index3, key3, val3); err != nil { - t.Fatal(err) - } - - m.recomputeHash() - - ap1 := m.Get(index1) - if ap1.Leaf.Value == nil { - t.Error("Cannot find key:", key1) - return - } - ap2 := m.Get(index2) - if ap2.Leaf.Value == nil { - t.Error("Cannot find key:", key2) - return - } - ap3 := m.Get(index3) - if ap3.Leaf.Value == nil { - t.Error("Cannot find key:", key3) - return - } +type mockProof struct { + key string + value []byte + index []byte + want ProofType +} - // proof of inclusion - proof := m.Get(index3) - if proof.Leaf.Value == nil { - t.Fatal("Expect returned leaf's value is not nil") - } - // ensure this is a proof of inclusion by comparing the returned indices - // and verifying the VRF index as well. - if !bytes.Equal(proof.LookupIndex, proof.Leaf.Index) || - !bytes.Equal(vrfPrivKey1.Compute([]byte(key3)), proof.LookupIndex) { - t.Fatal("Expect a proof of inclusion") - } - // verify auth path - if proof.Verify([]byte(key3), val3, m.hash) != nil { - t.Error("Proof of inclusion verification failed.") - } +// These characters are used to generate a random string containing only +// the uppercase and lowercase letters of the English alphabet. +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) } -func TestVerifyProofSamePrefix(t *testing.T) { - m, err := NewMerkleTree() - if err != nil { - t.Fatal(err) - } - - key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) - val1 := []byte("value1") - if err := m.Set(index1, key1, val1); err != nil { - t.Fatal(err) - } +var N uint64 = 3 // number of inclusions. + +// setupTestProofs creates a MerkleTree with `N` inclusions and +// searchs for a random index which shares the same prefix with an inclusion. +func setupTestProofs(t *testing.T) (*MerkleTree, []*mockProof) { + m := newEmptyTreeForTest(t) + + tuple := []*mockProof{} + for i := uint64(0); i < uint64(N); i++ { + key := keyPrefix + strconv.FormatUint(i, 10) + val := append(valuePrefix, byte(i)) + index := staticVRFKey.Compute([]byte(key)) + if err := m.Set(index, key, val); err != nil { + t.Fatal(err) + } + tuple = append(tuple, &mockProof{key, val, index, ProofOfInclusion}) + } + + sharedPrefix := tuple[0].index + var absentKey string + var absentIndex []byte + for { + absentKey = RandStringBytesMaskImprSrc(3) + absentIndex = staticVRFKey.Compute([]byte(absentKey)) + proof := m.Get(absentIndex) + // assert these indices share the same prefix in the first bit + if bytes.Equal(utils.ToBytes(utils.ToBits(sharedPrefix)[:proof.Leaf.Level]), + utils.ToBytes(utils.ToBits(absentIndex)[:proof.Leaf.Level])) { + break + } + } + + tuple = append(tuple, &mockProof{absentKey, nil, absentIndex, ProofOfAbsence}) m.recomputeHash() - absentIndex := vrfPrivKey1.Compute([]byte("a")) - proof := m.Get(absentIndex) // shares the same prefix with leaf node key1 - if proof.Leaf.Value != nil { - t.Fatal("Expect returned leaf's value is nil") - } - // ensure this is a proof of absence - if bytes.Equal(proof.LookupIndex, proof.Leaf.Index) || - !bytes.Equal(vrfPrivKey1.Compute([]byte("a")), proof.LookupIndex) { - t.Fatal("Expect a proof of absence") - } - // assert these indices share the same prefix in the first bit - if !bytes.Equal(utils.ToBytes(utils.ToBits(index1)[:proof.Leaf.Level]), - utils.ToBytes(utils.ToBits(absentIndex)[:proof.Leaf.Level])) { - t.Fatal("Expect these indices share the same prefix in the first bit") - } - if proof.Verify([]byte("a"), nil, m.hash) != nil { - t.Error("Proof of absence verification failed.") - } + return m, tuple +} - // re-get proof of inclusion - // for testing the commitment assignment - proof = m.Get(index1) - if proof.Leaf.Value == nil { - t.Fatal("Expect returned leaf's value is not nil") - } - // ensure this is a proof of inclusion by comparing the returned indices - // and verifying the VRF index as well. - if !bytes.Equal(proof.LookupIndex, proof.Leaf.Index) || - !bytes.Equal(vrfPrivKey1.Compute([]byte(key1)), proof.LookupIndex) { - t.Fatal("Expect a proof of inclusion") - } - // step 2. verify auth path - if proof.Verify([]byte(key1), val1, m.hash) != nil { - t.Error("Proof of inclusion verification failed.") +func TestVerifyProof(t *testing.T) { + m, tests := setupTestProofs(t) + + for _, tt := range tests { + proof := m.Get(tt.index) + if got, want := proof.ProofType(), tt.want; got != want { + t.Error("TestVerifyProof() failed with tuple(", tt.key, tt.value, ")") + } + if proof.Verify([]byte(tt.key), tt.value, m.hash) != nil { + t.Error("TestVerifyProof() failed with tuple(", tt.key, tt.value, ")") + } } } func TestProofVerificationErrors(t *testing.T) { - m, err := NewMerkleTree() - if err != nil { - t.Fatal(err) - } + m, tuple := setupTestProofs(t) - key1 := "key1" - index1 := vrfPrivKey1.Compute([]byte(key1)) - val1 := []byte("value1") - if err := m.Set(index1, key1, val1); err != nil { - t.Fatal(err) - } - m.recomputeHash() - absentIndex := vrfPrivKey1.Compute([]byte("a")) + index, key, value := tuple[0].index, tuple[0].key, tuple[0].value // ProofOfInclusion // assert proof of inclusion - proof1 := m.Get(index1) + proof1 := m.Get(index) if proof1.ProofType() != ProofOfInclusion { t.Fatal("Expect a proof of inclusion") } // - ErrBindingsDiffer proof1.Leaf.Value[0] += 1 - if err := proof1.Verify([]byte(key1), val1, m.hash); err != ErrBindingsDiffer { + if err := proof1.Verify([]byte(key), value, m.hash); err != ErrBindingsDiffer { t.Error("Expect", ErrBindingsDiffer, "got", err) } // - ErrUnverifiableCommitment proof1.Leaf.Value[0] -= 1 proof1.Leaf.Commitment.Salt[0] += 1 - if err := proof1.Verify([]byte(key1), val1, m.hash); err != ErrUnverifiableCommitment { + if err := proof1.Verify([]byte(key), value, m.hash); err != ErrUnverifiableCommitment { t.Error("Expect", ErrUnverifiableCommitment, "got", err) } // ErrUnequalTreeHashes hash := append([]byte{}, m.hash...) hash[0] += 1 proof1.Leaf.Commitment.Salt[0] -= 1 - if err := proof1.Verify([]byte(key1), val1, hash); err != ErrUnequalTreeHashes { + if err := proof1.Verify([]byte(key), value, hash); err != ErrUnequalTreeHashes { t.Error("Expect", ErrUnequalTreeHashes, "got", err) } // ProofOfAbsence - proof2 := m.Get(absentIndex) // shares the same prefix with leaf node key1 + index, key, value = tuple[N].index, tuple[N].key, tuple[N].value + proof2 := m.Get(index) // shares the same prefix with leaf node key1 // assert proof of absence if proof2.ProofType() != ProofOfAbsence { t.Fatal("Expect a proof of absence") } // - ErrBindingsDiffer proof2.Leaf.Value = make([]byte, 1) - if err := proof2.Verify([]byte("a"), nil, m.hash); err != ErrBindingsDiffer { + if err := proof2.Verify([]byte(key), value, m.hash); err != ErrBindingsDiffer { t.Error("Expect", ErrBindingsDiffer, "got", err) } // - ErrIndicesMismatch proof2.Leaf.Value = nil proof2.Leaf.Index[0] &= 0x01 - if err := proof2.Verify([]byte("a"), nil, m.hash); err != ErrIndicesMismatch { + if err := proof2.Verify([]byte(key), value, m.hash); err != ErrIndicesMismatch { t.Error("Expect", ErrIndicesMismatch, "got", err) } } diff --git a/merkletree/str_test.go b/merkletree/str_test.go index 82fb88d..5c57282 100644 --- a/merkletree/str_test.go +++ b/merkletree/str_test.go @@ -1,23 +1,19 @@ package merkletree -import "testing" +import ( + "testing" +) func TestVerifyHashChain(t *testing.T) { var N uint64 = 100 - keyPrefix := "key" - valuePrefix := []byte("value") - pad, err := NewPAD(TestAd{"abc"}, signKey, vrfPrivKey1, 10) + pad, err := NewPAD(TestAd{"abc"}, staticSigningKey, staticVRFKey, N) if err != nil { t.Fatal(err) } savedSTR := pad.LatestSTR() - - pk, ok := pad.signKey.Public() - if !ok { - t.Fatal("Couldn't retrieve public-key.") - } + pk, _ := pad.signKey.Public() for i := uint64(1); i < N; i++ { key := keyPrefix + string(i) diff --git a/merkletree/testutil.go b/merkletree/testutil.go new file mode 100644 index 0000000..5bc8e2a --- /dev/null +++ b/merkletree/testutil.go @@ -0,0 +1,42 @@ +package merkletree + +import ( + "testing" + + "github.com/coniks-sys/coniks-go/crypto" +) + +var keyPrefix = "key" +var valuePrefix = []byte("value") + +var staticSigningKey = crypto.NewStaticTestSigningKey() +var staticVRFKey = crypto.NewStaticTestVRFKey() + +// StaticPAD returns a pad with a static initial STR for _tests_. +func StaticPAD(t *testing.T, ad AssocData) *PAD { + pad, err := NewPAD(ad, staticSigningKey, staticVRFKey, 10) + if err != nil { + t.Fatal(err) + } + str := NewSTR(pad.signKey, pad.ad, staticTree(t), 0, []byte{}) + pad.latestSTR = str + pad.snapshots[0] = pad.latestSTR + return pad +} + +func staticTree(t *testing.T) *MerkleTree { + m, err := NewMerkleTree() + if err != nil { + t.Fatal(err) + } + m.nonce = []byte{} + return m +} + +func newEmptyTreeForTest(t *testing.T) *MerkleTree { + m, err := NewMerkleTree() + if err != nil { + t.Fatal(err) + } + return m +} diff --git a/protocol/auditlog/auditlog_test.go b/protocol/auditlog/auditlog_test.go index 4fcff98..e1c79ab 100644 --- a/protocol/auditlog/auditlog_test.go +++ b/protocol/auditlog/auditlog_test.go @@ -10,7 +10,7 @@ import ( func TestInsertEmptyHistory(t *testing.T) { // create basic test directory and audit log with 1 STR - _, _, _ = NewTestAuditLog(t, 0) + NewTestAuditLog(t, 0) } func TestUpdateHistory(t *testing.T) { @@ -32,7 +32,7 @@ func TestUpdateHistory(t *testing.T) { func TestInsertPriorHistory(t *testing.T) { // create basic test directory and audit log with 11 STRs - _, _, _ = NewTestAuditLog(t, 10) + NewTestAuditLog(t, 10) } func TestInsertExistingHistory(t *testing.T) { @@ -74,16 +74,16 @@ func TestAuditLogBadEpochRange(t *testing.T) { dirInitHash := auditor.ComputeDirectoryIdentity(hist[0]) h, _ := aud.get(dirInitHash) - err1 := h.Audit(resp) - if err1 != nil { - t.Fatalf("Error occurred while auditing STR history: %s", err1.Error()) + err := h.Audit(resp) + if err != nil { + t.Fatalf("Error occurred while auditing STR history: %s", err.Error()) } // now try to audit the same range again: should fail because the // verified epoch is at 1 - err1 = h.Audit(resp) - if err1 != protocol.CheckBadSTR { - t.Fatalf("Expecting CheckBadSTR, got %s", err1.Error()) + err = h.Audit(resp) + if err != protocol.CheckBadSTR { + t.Fatalf("Expecting CheckBadSTR, got %s", err.Error()) } } @@ -172,8 +172,8 @@ func TestGetObservedSTRMultipleEpochs(t *testing.T) { h, _ := aud.get(dirInitHash) resp := protocol.NewSTRHistoryRange([]*protocol.DirSTR{d.LatestSTR()}) - err1 := h.Audit(resp) - if err1 != nil { + err := h.Audit(resp) + if err != nil { t.Fatal("Error occurred updating audit log after auditing request") } @@ -339,9 +339,9 @@ func TestSTRHistoryRequestLatest(t *testing.T) { dirInitHash := auditor.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()) + err := h.Audit(resp) + if err != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err.Error()) } } @@ -375,9 +375,9 @@ func TestSTRHistoryRequestRangeLatest(t *testing.T) { dirInitHash := auditor.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()) + err := h.Audit(resp) + if err != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err.Error()) } } @@ -411,8 +411,8 @@ func TestSTRHistoryRequestInEpoch(t *testing.T) { dirInitHash := auditor.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()) + err := h.Audit(resp) + if err != nil { + t.Fatalf("Error occurred auditing the latest STR: %s", err.Error()) } } diff --git a/protocol/auditlog/testutil.go b/protocol/auditlog/testutil.go index 0086aad..a7f6c63 100644 --- a/protocol/auditlog/testutil.go +++ b/protocol/auditlog/testutil.go @@ -3,10 +3,13 @@ package auditlog import ( "testing" + "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/directory" ) +var staticSigningKey = crypto.NewStaticTestSigningKey() + // 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 @@ -15,21 +18,22 @@ import ( // STRs as it always includes the STR after the last directory update func NewTestAuditLog(t *testing.T, numEpochs int) ( *directory.ConiksDirectory, ConiksAuditLog, []*protocol.DirSTR) { - d, pk := directory.NewTestDirectory(t, true) + d := directory.NewTestDirectory(t) aud := New() - var hist []*protocol.DirSTR + var snaps []*protocol.DirSTR for ep := 0; ep < numEpochs; ep++ { - hist = append(hist, d.LatestSTR()) + snaps = append(snaps, d.LatestSTR()) d.Update() } // always include the actual latest STR - hist = append(hist, d.LatestSTR()) + snaps = append(snaps, d.LatestSTR()) - err := aud.InitHistory("test-server", pk, hist) + pk, _ := staticSigningKey.Public() + err := aud.InitHistory("test-server", pk, snaps) if err != nil { t.Fatalf("Error inserting a new history with %d STRs", numEpochs+1) } - return d, aud, hist + return d, aud, snaps } diff --git a/protocol/auditor/auditor.go b/protocol/auditor/auditor.go index 4d54bee..d6e2f0b 100644 --- a/protocol/auditor/auditor.go +++ b/protocol/auditor/auditor.go @@ -91,10 +91,6 @@ func (a *AudState) CheckSTRAgainstVerified(str *protocol.DirSTR) error { // 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 diff --git a/protocol/auditor/auditor_test.go b/protocol/auditor/auditor_test.go index 7d283f5..9f3f28e 100644 --- a/protocol/auditor/auditor_test.go +++ b/protocol/auditor/auditor_test.go @@ -3,13 +3,16 @@ package auditor import ( "testing" + "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/directory" ) +var staticSigningKey = crypto.NewStaticTestSigningKey() + func TestAuditBadSTRSignature(t *testing.T) { - // create basic test directory - d, pk := directory.NewTestDirectory(t, true) + d := directory.NewTestDirectory(t) + pk, _ := staticSigningKey.Public() // create a generic auditor state aud := New(pk, d.LatestSTR()) @@ -35,8 +38,8 @@ func TestAuditBadSTRSignature(t *testing.T) { // used to be TestVerifyWithError in consistencychecks_test.go func TestAuditBadSameEpoch(t *testing.T) { - // create basic test directory - d, pk := directory.NewTestDirectory(t, true) + d := directory.NewTestDirectory(t) + pk, _ := staticSigningKey.Public() // create a generic auditor state aud := New(pk, d.LatestSTR()) @@ -57,8 +60,8 @@ func TestAuditBadSameEpoch(t *testing.T) { } func TestAuditBadNewSTREpoch(t *testing.T) { - // create basic test directory - d, pk := directory.NewTestDirectory(t, true) + d := directory.NewTestDirectory(t) + pk, _ := staticSigningKey.Public() // create a generic auditor state aud := New(pk, d.LatestSTR()) @@ -95,8 +98,8 @@ func TestAuditBadNewSTREpoch(t *testing.T) { } func TestAuditMalformedSTRRange(t *testing.T) { - // create basic test directory - d, pk := directory.NewTestDirectory(t, true) + d := directory.NewTestDirectory(t) + pk, _ := staticSigningKey.Public() // create a generic auditor state aud := New(pk, d.LatestSTR()) diff --git a/protocol/auditor/common_test.go b/protocol/auditor/common_test.go index 613e9c6..642dcff 100644 --- a/protocol/auditor/common_test.go +++ b/protocol/auditor/common_test.go @@ -1,43 +1,48 @@ package auditor import ( + "bytes" + "encoding/hex" "testing" - "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/protocol" "github.com/coniks-sys/coniks-go/protocol/directory" ) func TestComputeDirectoryIdentity(t *testing.T) { - d, _ := directory.NewTestDirectory(t, true) - // str0 := d.LatestSTR() + d := directory.NewTestDirectory(t) + str0 := d.LatestSTR() d.Update() str1 := d.LatestSTR() - var unknown [crypto.HashSizeByte]byte - type args struct { - str *protocol.DirSTR - } - tests := []struct { + + for _, tc := range []struct { name string - args args - want [crypto.HashSizeByte]byte + str *protocol.DirSTR + want []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" { + {"normal", str0, hex2bin("fd0584f79054f8113f21e5450e0ad21c9221fc159334c7bc1644e3e2a0fb5060")}, + {"panic", str1, []byte{}}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.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) + if got, want := ComputeDirectoryIdentity(tc.str), tc.want; !bytes.Equal(got[:], want) { + t.Errorf("ComputeDirectoryIdentity() = %#v, want %#v", got, want) } }) } } + +// decode hex string to byte array +func hex2bin(h string) []byte { + result, err := hex.DecodeString(h) + if err != nil { + panic(err) + } + return result +} diff --git a/protocol/client/consistencychecks_test.go b/protocol/client/consistencychecks_test.go index 153b93f..da13c8e 100644 --- a/protocol/client/consistencychecks_test.go +++ b/protocol/client/consistencychecks_test.go @@ -1,198 +1 @@ package client - -import ( - "bytes" - "testing" - - "github.com/coniks-sys/coniks-go/protocol" - "github.com/coniks-sys/coniks-go/protocol/directory" -) - -var ( - alice = "alice" - bob = "bob" - key = []byte("key") -) - -func registerAndVerify(d *directory.ConiksDirectory, cc *ConsistencyChecks, - name string, key []byte) error { - request := &protocol.RegistrationRequest{ - Username: name, - Key: key, - } - res := d.Register(request) - return cc.HandleResponse(protocol.RegistrationType, res, name, key) -} - -func lookupAndVerify(d *directory.ConiksDirectory, cc *ConsistencyChecks, - name string, key []byte) error { - request := &protocol.KeyLookupRequest{ - Username: name, - } - res := d.KeyLookup(request) - return cc.HandleResponse(protocol.KeyLookupType, res, name, key) -} - -func TestMalformedClientMessage(t *testing.T) { - d, pk := directory.NewTestDirectory(t, true) - cc := New(d.LatestSTR(), true, pk) - - request := &protocol.RegistrationRequest{ - Username: "", // invalid username - Key: key, - } - res := d.Register(request) - if err := cc.HandleResponse(protocol.RegistrationType, res, "", key); err != protocol.ErrMalformedMessage { - t.Error("Unexpected verification result", - "got", err) - } -} - -func TestMalformedDirectoryMessage(t *testing.T) { - d, pk := directory.NewTestDirectory(t, true) - cc := New(d.LatestSTR(), true, pk) - - request := &protocol.RegistrationRequest{ - Username: "alice", - Key: key, - } - res := d.Register(request) - // modify response message - res.DirectoryResponse.(*protocol.DirectoryProof).STR = nil - if err := cc.HandleResponse(protocol.RegistrationType, res, "alice", key); err != protocol.ErrMalformedMessage { - t.Error("Unexpected verification result") - } -} - -func TestVerifyRegistrationResponseWithTB(t *testing.T) { - d, pk := directory.NewTestDirectory(t, true) - - cc := New(d.LatestSTR(), true, pk) - - if err := registerAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - - if len(cc.TBs) != 1 { - t.Fatal("Expect the directory to return a signed promise") - } - - // test error name existed - if err := registerAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - - // test error name existed with different key - if err := registerAndVerify(d, cc, alice, []byte{1, 2, 3}); err != protocol.CheckBindingsDiffer { - t.Error(err) - } - if len(cc.TBs) != 1 { - t.Fatal("Expect the directory to return a signed promise") - } - - // re-register in a different epoch - // Since the fulfilled promise verification would be perform - // when the client is monitoring, we do _not_ expect a TB's verification here. - d.Update() - - if err := registerAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - if err := registerAndVerify(d, cc, alice, []byte{1, 2, 3}); err != protocol.CheckBindingsDiffer { - t.Error(err) - } -} - -func TestVerifyFullfilledPromise(t *testing.T) { - d, pk := directory.NewTestDirectory(t, true) - - cc := New(d.LatestSTR(), true, pk) - - if err := registerAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - if err := registerAndVerify(d, cc, bob, key); err != nil { - t.Error(err) - } - - if len(cc.TBs) != 2 { - t.Fatal("Expect the directory to return signed promises") - } - - d.Update() - - for i := 0; i < 2; i++ { - if err := lookupAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - } - - // should contain the TBs of bob - if len(cc.TBs) != 1 || cc.TBs[bob] == nil { - t.Error("Expect the directory to insert the binding as promised") - } - - if err := lookupAndVerify(d, cc, bob, key); err != nil { - t.Error(err) - } - if len(cc.TBs) != 0 { - t.Error("Expect the directory to insert the binding as promised") - } -} - -func TestVerifyKeyLookupResponseWithTB(t *testing.T) { - d, pk := directory.NewTestDirectory(t, true) - - cc := New(d.LatestSTR(), true, pk) - - // do lookup first - if err := lookupAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - - // register - if err := registerAndVerify(d, cc, alice, key); err != nil { - t.Error(err) - } - - // do lookup in the same epoch - TB TOFU - // and get the key from the response. The key would be extracted from the TB - request := &protocol.KeyLookupRequest{ - Username: alice, - } - res := d.KeyLookup(request) - if res.Error != protocol.ReqSuccess { - t.Error("Expect", protocol.ReqSuccess, "got", res.Error) - } - if err := cc.HandleResponse(protocol.KeyLookupType, res, alice, nil); err != nil { - t.Error("Expect", nil, "got", err) - } - recvKey, e := res.GetKey() - if e != nil && !bytes.Equal(recvKey, key) { - t.Error("The directory has returned a wrong key.") - } - - d.Update() - - // do lookup in the different epoch - // this time, the key would be extracted from the AP. - request = &protocol.KeyLookupRequest{ - Username: alice, - } - res = d.KeyLookup(request) - if res.Error != protocol.ReqSuccess { - t.Error("Expect", protocol.ReqSuccess, "got", res.Error) - } - if err := cc.HandleResponse(protocol.KeyLookupType, res, alice, nil); err != nil { - t.Error("Expect nil", "got", err) - } - recvKey, e = res.GetKey() - if e != nil && !bytes.Equal(recvKey, key) { - t.Error("The directory has returned a wrong key.") - } - - // test error name not found - if err := lookupAndVerify(d, cc, bob, key); err != nil { - t.Error(err) - } -} diff --git a/protocol/directory/directory_test.go b/protocol/directory/directory_test.go index 3482085..ffdef7b 100644 --- a/protocol/directory/directory_test.go +++ b/protocol/directory/directory_test.go @@ -1,270 +1,13 @@ package directory import ( - "bytes" "testing" "github.com/coniks-sys/coniks-go/protocol" ) -func TestRegisterWithTB(t *testing.T) { - // expect return a proof of absence - // along with a TB of registering user - d, _ := NewTestDirectory(t, true) - - res := d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - df := res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqSuccess { - t.Fatal("Unable to register") - } - if ap := df.AP[0]; ap == nil || bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { - t.Fatal("Expect a proof of absence") - } - if df.TB == nil { - t.Fatal("Expect returned TB is not nil") - } - if d.tbs["alice"] == nil { - t.Fatal("Expect TBs array has registering user") - } - d.Update() - if len(d.tbs) != 0 { - t.Fatal("Expect TBs array is empty") - } -} - -func TestRegisterExistedUserWithTB(t *testing.T) { - d, _ := NewTestDirectory(t, true) - res := d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - if res.Error != protocol.ReqSuccess { - t.Fatal("Unable to register") - } - // register in the same epoch - // expect return a proof of absence - // along with a TB of registering user - // and error ReqNameExisted - res = d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - df := res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqNameExisted { - t.Fatal("Expect error code", protocol.ReqNameExisted, "got", res.Error) - } - if ap := df.AP[0]; ap == nil || bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { - t.Fatal("Expect a proof of absence") - } - if df.TB == nil { - t.Fatal("Expect returned TB is not nil") - } - - d.Update() - // register in different epochs - // expect return a proof of inclusion - // and error ReqNameExisted - res = d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - df = res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqNameExisted { - t.Fatal("Expect error code", protocol.ReqNameExisted, "got", res.Error) - } - if ap := df.AP[0]; ap == nil || !bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { - t.Fatal("Expect a proof of inclusion") - } - if df.TB != nil { - t.Fatal("Expect returned TB is nil") - } -} - -func TestNewDirectoryPanicWithoutTB(t *testing.T) { - // workaround for #110 - defer func() { - if r := recover(); r == nil { - t.Errorf("The code did not panic") - } - }() - - NewTestDirectory(t, false) -} - -func TestKeyLookupWithTB(t *testing.T) { - d, _ := NewTestDirectory(t, true) - res := d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - tb := res.DirectoryResponse.(*protocol.DirectoryProof).TB - // lookup in the same epoch - // expect a proof of absence and the TB of looking up user - res = d.KeyLookup(&protocol.KeyLookupRequest{Username: "alice"}) - df := res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqSuccess { - t.Fatal("Expect no error", "got", res.Error) - } - if ap := df.AP[0]; ap == nil || bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { - t.Fatal("Expect a proof of absence") - } - if df.TB == nil || !bytes.Equal(df.TB.Value, []byte("key")) { - t.Fatal("Expect the TB of looking up user") - } - // assert that the server returns the same TB - if !bytes.Equal(df.TB.Signature, tb.Signature) || - !bytes.Equal(df.TB.Index, tb.Index) || - !bytes.Equal(df.TB.Value, tb.Value) { - t.Fatal("Expect the same TB for the registration and lookup") - } - - d.Update() - // lookup in epoch after registering epoch - // expect a proof of inclusion - res = d.KeyLookup(&protocol.KeyLookupRequest{Username: "alice"}) - df = res.DirectoryResponse.(*protocol.DirectoryProof) - if ap := df.AP[0]; ap == nil || !bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { - t.Fatal("Expect a proof of inclusion") - } - if df.TB != nil { - t.Fatal("Expect returned TB is nil") - } -} - -func TestDirectoryMonitoring(t *testing.T) { - N := 10 - - d, _ := NewTestDirectory(t, true) - d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - - d.Update() - savedSTR := d.LatestSTR() - for i := 2; i < N; i++ { - d.Update() - } - - // missed from epoch 2 - res := d.Monitor(&protocol.MonitoringRequest{ - Username: "alice", StartEpoch: uint64(2), EndEpoch: d.LatestSTR().Epoch, - }) - df := res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqSuccess { - t.Fatal("Unable to perform key lookup in epoch", 2) - } - expectNumberOfSTR := 10 - 2 - if len(df.AP) != expectNumberOfSTR || len(df.STR) != expectNumberOfSTR { - t.Fatal("Expect", expectNumberOfSTR, "auth paths/STRs", "got", len(df.AP), "auth paths and", len(df.STR), "STRs") - } - - for i := 0; i < expectNumberOfSTR; i++ { - str := df.STR[i] - if !str.VerifyHashChain(savedSTR) { - t.Fatal("Hash chain does not verify at epoch", i) - } - // we can ignore the auth path verification - // since it is already tested in merkletree package - savedSTR = str - } - - // assert the number of STRs returned is correct - res = d.Monitor(&protocol.MonitoringRequest{ - Username: "alice", StartEpoch: uint64(2), EndEpoch: d.LatestSTR().Epoch + 5, - }) - df = res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqSuccess { - t.Fatal("Unable to perform key lookup in epoch", 2) - } - if len(df.AP) != expectNumberOfSTR || len(df.STR) != expectNumberOfSTR { - t.Fatal("Expect", expectNumberOfSTR, "auth paths/STRs", "got", len(df.AP), "auth paths and", len(df.STR), "STRs") - } -} - -func TestDirectoryKeyLookupInEpoch(t *testing.T) { - N := 3 - - d, _ := NewTestDirectory(t, true) - for i := 0; i < N; i++ { - d.Update() - } - - // lookup at epoch 1, expect a proof of absence & ReqNameNotFound - res := d.KeyLookupInEpoch(&protocol.KeyLookupInEpochRequest{Username: "alice", Epoch: uint64(1)}) - df := res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqNameNotFound { - t.Fatal("Expect error", protocol.ReqNameNotFound, "got", res.Error) - } - if len(df.AP) != 1 { - t.Fatal("Expect only 1 auth path in response") - } - if len(df.STR) != int(d.LatestSTR().Epoch) { - t.Fatal("Expect", d.LatestSTR().Epoch, "STRs", "got", len(df.STR)) - } - - d.Register(&protocol.RegistrationRequest{ - Username: "alice", - Key: []byte("key")}) - for i := 0; i < N; i++ { - d.Update() - } - - res = d.KeyLookupInEpoch(&protocol.KeyLookupInEpochRequest{Username: "alice", Epoch: uint64(5)}) - df = res.DirectoryResponse.(*protocol.DirectoryProof) - if res.Error != protocol.ReqSuccess { - t.Fatal("Expect error", protocol.ReqSuccess, "got", res.Error) - } - if len(df.AP) != 1 { - t.Fatal("Expect only 1 auth path in response") - } - if len(df.STR) != 2 { - t.Fatal("Expect", 2, "STRs", "got", len(df.STR)) - } -} - -func TestDirectoryKeyLookupInEpochBadEpoch(t *testing.T) { - N := 3 - - d, _ := NewTestDirectory(t, true) - for i := 0; i < N; i++ { - d.Update() - } - - // Send an invalid KeyLookupInEpochRequest (epoch > d.LatestEpoch()) - // Expect ErrMalformedMessage - res := d.KeyLookupInEpoch(&protocol.KeyLookupInEpochRequest{Username: "alice", Epoch: uint64(6)}) - if res.Error != protocol.ErrMalformedMessage { - t.Fatal("Expect error", protocol.ErrMalformedMessage, "got", res.Error) - } -} - -func TestMonitoringBadStartEpoch(t *testing.T) { - N := 3 - - d, _ := NewTestDirectory(t, true) - for i := 0; i < N; i++ { - d.Update() - } - - // Send an invalid MonitoringRequest (startEpoch > d.LatestEpoch()) - // Expect ErrMalformedMessage - res := d.Monitor(&protocol.MonitoringRequest{ - Username: "alice", StartEpoch: uint64(6), EndEpoch: uint64(10), - }) - if res.Error != protocol.ErrMalformedMessage { - t.Fatal("Expect error", protocol.ErrMalformedMessage, "got", res.Error) - } - - // Send an invalid MonitoringRequest (startEpoch > EndEpoch) - // Expect ErrMalformedMessage - res = d.Monitor(&protocol.MonitoringRequest{ - Username: "alice", StartEpoch: uint64(2), EndEpoch: uint64(0), - }) - if res.Error != protocol.ErrMalformedMessage { - t.Fatal("Expect error", protocol.ErrMalformedMessage, "got", res.Error) - } -} - func TestPoliciesChanges(t *testing.T) { - d, _ := NewTestDirectory(t, true) + d := NewTestDirectory(t) if p := d.LatestSTR().Policies.EpochDeadline; p != 1 { t.Fatal("Unexpected policies", "want", 1, "got", p) } @@ -290,25 +33,71 @@ func TestPoliciesChanges(t *testing.T) { } } -func TestSTRHistoryRequestBadRange(t *testing.T) { - // create basic test directory - d, _ := NewTestDirectory(t, true) - - d.Update() - - res := d.GetSTRHistory(&protocol.STRHistoryRequest{ - StartEpoch: uint64(4), - EndEpoch: uint64(2)}) +func TestDirectoryKeyLookupInEpochBadEpoch(t *testing.T) { + d := NewTestDirectory(t) + for _, tc := range []struct { + name string + userName string + ep uint64 + want error + }{ + {"invalid username", "", 0, protocol.ErrMalformedMessage}, + {"bad end epoch", "Alice", 2, protocol.ErrMalformedMessage}, + } { + res := d.KeyLookupInEpoch(&protocol.KeyLookupInEpochRequest{ + Username: tc.userName, + Epoch: tc.ep, + }) + if res.Error != tc.want { + t.Errorf("Expect ErrMalformedMessage for %s", tc.name) + } + } +} - if res.Error != protocol.ErrMalformedMessage { - t.Fatal("Expect ErrMalformedMessage for bad STR history end epoch") +func TestBadRequestMonitoring(t *testing.T) { + d := NewTestDirectory(t) + + for _, tc := range []struct { + name string + userName string + startEp uint64 + endEp uint64 + want error + }{ + {"invalid username", "", 0, 0, protocol.ErrMalformedMessage}, + {"bad end epoch", "Alice", 4, 2, protocol.ErrMalformedMessage}, + {"out-of-bounds", "Alice", 2, d.LatestSTR().Epoch, protocol.ErrMalformedMessage}, + } { + res := d.Monitor(&protocol.MonitoringRequest{ + Username: tc.userName, + StartEpoch: tc.startEp, + EndEpoch: tc.endEp, + }) + if res.Error != tc.want { + t.Errorf("Expect ErrMalformedMessage for %s", tc.name) + } } +} - res = d.GetSTRHistory(&protocol.STRHistoryRequest{ - StartEpoch: uint64(6), - EndEpoch: uint64(d.LatestSTR().Epoch)}) +func TestBadRequestGetSTRHistory(t *testing.T) { + d := NewTestDirectory(t) + d.Update() - if res.Error != protocol.ErrMalformedMessage { - t.Fatal("Expect ErrMalformedMessage for out-of-bounds STR history") + for _, tc := range []struct { + name string + startEp uint64 + endEp uint64 + want error + }{ + {"bad end epoch", 4, 2, protocol.ErrMalformedMessage}, + {"out-of-bounds", 6, d.LatestSTR().Epoch, protocol.ErrMalformedMessage}, + } { + res := d.GetSTRHistory(&protocol.STRHistoryRequest{ + StartEpoch: tc.startEp, + EndEpoch: tc.endEp, + }) + if res.Error != tc.want { + t.Errorf("Expect ErrMalformedMessage for %s", tc.name) + } } } diff --git a/protocol/directory/testutil.go b/protocol/directory/testutil.go index 527beb5..afd9429 100644 --- a/protocol/directory/testutil.go +++ b/protocol/directory/testutil.go @@ -3,29 +3,16 @@ package directory import ( "testing" - "github.com/coniks-sys/coniks-go/crypto/sign" - "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/crypto" + "github.com/coniks-sys/coniks-go/merkletree" ) -// TODO: refactor the function signature after resolving #47 - // NewTestDirectory creates a ConiksDirectory used for testing server-side // CONIKS operations. -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) - } - signKey, err := sign.GenerateKey(nil) - if err != nil { - t.Fatal(err) - } - pk, _ := signKey.Public() - // epDeadline merkletree.TimeStamp, vrfKey vrf.PrivateKey, - // signKey sign.PrivateKey, dirSize uint64, useTBs bool - d := New(1, vrfKey, signKey, 10, useTBs) - return d, pk +func NewTestDirectory(t *testing.T) *ConiksDirectory { + vrfKey := crypto.NewStaticTestVRFKey() + signKey := crypto.NewStaticTestSigningKey() + d := New(1, vrfKey, signKey, 10, true) + d.pad = merkletree.StaticPAD(t, d.policies) + return d } diff --git a/protocol/doc.go b/protocol/doc.go index baae492..fea7a97 100644 --- a/protocol/doc.go +++ b/protocol/doc.go @@ -8,7 +8,18 @@ an API for maintaining an auditable, privacy-preserving key directory on a server, as well as an API for checking the consistency of the directory at the client. -Consistency Checks +Auditlog + +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. + +Auditor + +This module implements a generic CONIKS auditor, that is all the functionality +that clients and auditors need to verify a server's STR history. + +Client This module implements all consistency checks performed by a CONIKS client on directory proofs received from a CONIKS server. These operations @@ -24,6 +35,11 @@ to public keys. It currently supports registration of new mappings, latest-version key lookups, historical key lookups, and monitoring of mappings. +Tests + +This module contains integration test cases for CONIKS directory, CONIKS client +and CONIKS auditor modules. + Error This module defines the constants representing the types diff --git a/protocol/error.go b/protocol/error.go index 2521423..8c9f74d 100644 --- a/protocol/error.go +++ b/protocol/error.go @@ -40,12 +40,12 @@ const ( CheckBrokenPromise ) -// Errors contains codes indicating the client +// errors contains codes indicating the client // should skip the consistency checks. These errors indicate // that either a client request could not be processed due to // a malformed client request, an internal server error or // due to a malformed server response. -var Errors = map[error]bool{ +var errors = map[error]bool{ ErrMalformedMessage: true, ErrDirectory: true, ErrAuditLog: true, diff --git a/protocol/message.go b/protocol/message.go index 9bc46ed..bafc86a 100644 --- a/protocol/message.go +++ b/protocol/message.go @@ -269,9 +269,13 @@ func NewSTRHistoryRange(str []*DirSTR) *Response { // Validate returns immediately if the message includes an error code. // Otherwise, it verifies whether the message has proper format. func (msg *Response) Validate() error { - if Errors[msg.Error] { + if errors[msg.Error] { return msg.Error } + + if msg.DirectoryResponse == nil { + return ErrMalformedMessage + } switch df := msg.DirectoryResponse.(type) { case *DirectoryProof: if len(df.STR) == 0 || len(df.AP) == 0 { diff --git a/protocol/tests/client_auditlog_test.go b/protocol/tests/client_auditlog_test.go new file mode 100644 index 0000000..ca8701d --- /dev/null +++ b/protocol/tests/client_auditlog_test.go @@ -0,0 +1 @@ +package tests diff --git a/protocol/tests/directory_auditlog_test.go b/protocol/tests/directory_auditlog_test.go new file mode 100644 index 0000000..ca8701d --- /dev/null +++ b/protocol/tests/directory_auditlog_test.go @@ -0,0 +1 @@ +package tests diff --git a/protocol/tests/directory_client_test.go b/protocol/tests/directory_client_test.go new file mode 100644 index 0000000..ca8701d --- /dev/null +++ b/protocol/tests/directory_client_test.go @@ -0,0 +1 @@ +package tests diff --git a/protocol/tests/testutil.go b/protocol/tests/testutil.go new file mode 100644 index 0000000..ca8701d --- /dev/null +++ b/protocol/tests/testutil.go @@ -0,0 +1 @@ +package tests