You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
4.6 KiB
121 lines
4.6 KiB
package capsule_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/capsule"
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
"g.activething.com/go/DateKeys/profile"
|
|
"g.activething.com/go/DateKeys/provider"
|
|
)
|
|
|
|
// supplierFunc adapts a function to provider.Supplier.
|
|
type supplierFunc func(*profile.Profile, provider.Condition) ([]byte, error)
|
|
|
|
func (f supplierFunc) Supply(p *profile.Profile, c provider.Condition) ([]byte, error) {
|
|
return f(p, c)
|
|
}
|
|
|
|
func readRelease(t *testing.T, round uint64) []byte {
|
|
t.Helper()
|
|
b, err := os.ReadFile(filepath.Join("../testdata/releases", testkit.ReleaseFileName(round)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Spec v0.15, §63 step 9.c: a release in hand is not compared with the
|
|
// clock. The capsule opens with a clock before the round time, Opened says
|
|
// the clock is behind, and Opened.Release encodes to the official release
|
|
// object of the round.
|
|
func TestReleaseInHand(t *testing.T) {
|
|
f := loadFixture(t, "format3_time_and_key_portable")
|
|
obj := readRelease(t, 1000)
|
|
for _, tc := range []struct {
|
|
name string
|
|
now time.Time
|
|
behind bool
|
|
}{
|
|
{"after the round time", f.unlock(t), false},
|
|
{"a clock one nanosecond behind", f.unlock(t).Add(-1), true},
|
|
{"a clock years behind", testkit.Genesis(), true},
|
|
} {
|
|
o := f.openOptions(t)
|
|
o.Source, o.Release, o.Now = nil, provider.Encoded(obj), testkit.Fixed(tc.now)
|
|
out, err := capsule.Open(context.Background(), nil, bytes.NewReader(f.dkc), o)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", tc.name, err)
|
|
}
|
|
if out.ClockBehind != tc.behind {
|
|
t.Fatalf("%s: ClockBehind %v", tc.name, out.ClockBehind)
|
|
}
|
|
saved, err := provider.EncodeRelease(out.Release)
|
|
if err != nil || !bytes.Equal(saved, obj) {
|
|
t.Fatalf("%s: the release encodes to %x, %v", tc.name, saved, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A release in hand keeps the order of step 9: the credentials first (9.a,
|
|
// 9.b), with no request; then the release, whose failure to be supplied is
|
|
// ErrReleaseUnavailable alone at step 9; then step 10 with its codes, the
|
|
// layers of the object first.
|
|
func TestReleaseInHandErrors(t *testing.T) {
|
|
f := loadFixture(t, "format3_time_and_key_portable")
|
|
other := profile.Quicknet().ChainHash
|
|
other[5] ^= 1
|
|
otherChain, err := provider.EncodeRelease(provider.Release{Round: 1001, Signature: testkit.Release(1001).Signature, ChainHash: other[:]})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
supply func() ([]byte, error)
|
|
noAccess bool
|
|
want *datekeys.Error
|
|
step int
|
|
calls int
|
|
}{
|
|
{"no credential, before any supply", func() ([]byte, error) { return readRelease(t, 1000), nil }, true, datekeys.ErrAccessRequired, 9, 0},
|
|
{"nothing supplied", func() ([]byte, error) { return nil, fmt.Errorf("none: %w", datekeys.ErrReleaseUnavailable) }, false, datekeys.ErrReleaseUnavailable, 9, 1},
|
|
{"a supplier failing with another code", func() ([]byte, error) { return nil, fmt.Errorf("odd: %w", datekeys.ErrIntegrity) }, false, datekeys.ErrReleaseUnavailable, 9, 1},
|
|
{"an empty object", func() ([]byte, error) { return nil, nil }, false, datekeys.ErrNonCanonicalCBOR, 10, 1},
|
|
{"another chain and another round", func() ([]byte, error) { return otherChain, nil }, false, datekeys.ErrProfileMismatch, 10, 1},
|
|
{"another round", func() ([]byte, error) { return readRelease(t, 1001), nil }, false, datekeys.ErrRoundMismatch, 10, 1},
|
|
{"drand's JSON of another round", func() ([]byte, error) {
|
|
return []byte(`{"round":1001,"signature":"00"}`), nil
|
|
}, false, datekeys.ErrRoundMismatch, 10, 1},
|
|
} {
|
|
calls := 0
|
|
o := f.openOptions(t)
|
|
o.Source, o.Now = nil, testkit.Fixed(testkit.Genesis())
|
|
o.Release = supplierFunc(func(*profile.Profile, provider.Condition) ([]byte, error) { calls++; return tc.supply() })
|
|
if tc.noAccess {
|
|
o.AccessKey = nil
|
|
}
|
|
step, err := openAt(t, f.dkc, o)
|
|
if !onlyCode(err, tc.want) || step != tc.step || calls != tc.calls {
|
|
t.Errorf("%s: got %v at step %d after %d supplies, want %v at step %d after %d", tc.name, err, step, calls, tc.want, tc.step, tc.calls)
|
|
}
|
|
}
|
|
|
|
o := f.openOptions(t)
|
|
o.Release = provider.Encoded(readRelease(t, 1000))
|
|
if _, err := capsule.Open(context.Background(), nil, bytes.NewReader(f.dkc), o); err == nil || datekeys.Code(err) != "" {
|
|
t.Fatalf("Source and Release together: %v", err)
|
|
}
|
|
o.Source, o.Release = nil, nil
|
|
if _, err := capsule.Open(context.Background(), nil, bytes.NewReader(f.dkc), o); err == nil || errors.Is(err, datekeys.ErrReleaseUnavailable) {
|
|
t.Fatalf("no source: %v", err)
|
|
}
|
|
}
|