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.
DateKeys/capsule/diagnostics_test.go

135 lines
4.2 KiB

package capsule_test
import (
"bytes"
"context"
"encoding/hex"
"errors"
"io"
"strings"
"testing"
"github.com/drand/drand/v2/common"
"github.com/drand/tlock"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
)
// Spec §63 step 11: when the IBE check of the tlock stanza fails, kyber
// reports in its error the candidate plaintext, W xor H4(sigma), and r. A
// third party who edits W learns FK_TIME from the candidate and the edit, so
// neither the error of Open nor the details of its checks may carry them:
// the text of tlock and kyber is never copied.
func TestTlockFailureDiagnosticsCarryNoSecrets(t *testing.T) {
e, err := testkit.NewMutationEnv(fixtureDir)
if err != nil {
t.Fatal(err)
}
f := e.TimeOnly
fk, err := f.TimeFileKey()
if err != nil {
t.Fatal(err)
}
// W ends the stanza body: flipping its last bit flips the last bit of
// the candidate plaintext, sigma being unchanged.
in, err := f.WithTlockBody(func(b []byte) ([]byte, error) {
c := bytes.Clone(b)
c[len(c)-1] ^= 1
return c, nil
})
if err != nil {
t.Fatal(err)
}
candidate := bytes.Clone(fk)
candidate[len(candidate)-1] ^= 1
secrets := map[string]string{
"FK_TIME": string(fk),
"FK_TIME in hex": hex.EncodeToString(fk),
"candidate plaintext": string(candidate),
"candidate plaintext in hex": hex.EncodeToString(candidate),
}
// What tlock reports for this body: kyber's error, with the candidate
// and r as kyber prints it.
parts, err := testkit.Split(in.DKC)
if err != nil {
t.Fatal(err)
}
stanzas, err := agewrap.Stanzas(bytes.NewReader(parts.Sealed))
if err != nil {
t.Fatal(err)
}
p := profile.Quicknet()
scheme, err := p.DrandScheme()
if err != nil {
t.Fatal(err)
}
key := scheme.KeyGroup.Point()
if err := key.UnmarshalBinary(p.PublicKey); err != nil {
t.Fatal(err)
}
ct, err := tlock.BytesToCiphertext(*scheme, stanzas[0].Body)
if err != nil {
t.Fatal(err)
}
_, tlockErr := tlock.TimeUnlock(*scheme, key, common.Beacon{Round: f.Published.Round, Signature: f.Published.Signature}, ct)
if tlockErr == nil {
t.Fatal("tlock accepts the edited W")
}
if msg := tlockErr.Error(); strings.Contains(msg, string(candidate)) {
if i := strings.LastIndex(msg, ", r "); i >= 0 {
r := msg[i+len(", r "):]
secrets["r as kyber prints it"] = r
if b, err := hex.DecodeString(r); err == nil {
secrets["r"] = string(b)
}
}
} else {
t.Logf("tlock no longer reports the candidate plaintext: %q", msg)
}
opened, err := capsule.Open(context.Background(), io.Discard, bytes.NewReader(in.DKC), capsule.OpenOptions{
Registry: testkit.Registry(), Source: testkit.NewSource(f.Published), Now: testkit.Fixed(f.Unlock),
})
checks := opened.Inspection.Checks
if last := checks[len(checks)-1]; !errors.Is(err, datekeys.ErrIntegrity) || last.Step != 11 || last.Error != "ERR_INTEGRITY" {
t.Fatalf("got %v at step %d, want ERR_INTEGRITY at step 11", err, last.Step)
}
texts := []string{err.Error()}
for _, c := range checks {
texts = append(texts, c.Detail)
}
for name, s := range secrets {
for _, text := range texts {
if strings.Contains(text, s) {
t.Errorf("the %s is in %q", name, text)
}
}
}
}
// The failures of age get fixed reasons, but a failure of the caller's
// writer at step 17 is not one of age: it keeps its own text, and the code
// it always had.
func TestPlaintextWriterFailureKeepsItsText(t *testing.T) {
e, err := testkit.NewMutationEnv(fixtureDir)
if err != nil {
t.Fatal(err)
}
f := e.TimeOnly
opened, err := capsule.Open(context.Background(), failingWriter{}, bytes.NewReader(f.DKC), capsule.OpenOptions{
Registry: testkit.Registry(), Source: testkit.NewSource(f.Published), Now: testkit.Fixed(f.Unlock),
})
checks := opened.Inspection.Checks
if last := checks[len(checks)-1]; !errors.Is(err, datekeys.ErrIntegrity) || last.Step != 17 {
t.Fatalf("got %v at step %d, want ERR_INTEGRITY at step 17", err, last.Step)
}
if !strings.Contains(err.Error(), "disk full") || strings.Contains(err.Error(), "STREAM") {
t.Fatalf("the error of the writer is not reported as such: %v", err)
}
}

Powered by TurnKey Linux.