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.
146 lines
5.8 KiB
146 lines
5.8 KiB
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
)
|
|
|
|
const releases = "../../testdata/releases"
|
|
|
|
// unlock1000 is the round time of round 1000, that of time_only.dkc.
|
|
var unlock1000 = time.Date(2023, 8, 23, 15, 59, 24, 0, time.UTC)
|
|
|
|
// Spec v0.15, §63 step 9.c: a release in hand opens the capsule without any
|
|
// network request, even with a clock behind the round time, which decrypt
|
|
// only warns about; -save-release keeps it as a .dkr, the same bytes as the
|
|
// official one.
|
|
func TestDecryptWithReleaseInHand(t *testing.T) {
|
|
dir := t.TempDir()
|
|
want, err := os.ReadFile(filepath.Join(fixtures, "time_only.plaintext"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
jsonFile := filepath.Join(dir, "1000.json")
|
|
if err := os.WriteFile(jsonFile, []byte(`{"round":1000,"signature":"`+hex.EncodeToString(testkit.Release(1000).Signature)+`"}`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, c := range []struct {
|
|
name, release string
|
|
now time.Time
|
|
}{
|
|
{".dkr", filepath.Join(releases, "1000.dkr"), later},
|
|
{".dkr and a clock behind", filepath.Join(releases, "1000.dkr"), unlock1000.Add(-time.Hour)},
|
|
{"drand's JSON", jsonFile, later},
|
|
{"a local archive", filepath.Join(releases, testkit.ArchiveFile), unlock1000.Add(-time.Nanosecond)},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
out := filepath.Join(dir, c.name)
|
|
saved := filepath.Join(dir, "saved"+string(rune('a'+i))+".dkr")
|
|
_, stderr, err := cli(t, c.now, "decrypt", "-in", filepath.Join(fixtures, "time_only.dkc"), "-out", out, "-release", c.release, "-save-release", saved)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, _ := os.ReadFile(out); !bytes.Equal(got, want) {
|
|
t.Fatal("wrong content")
|
|
}
|
|
if behind := strings.Contains(stderr, "may be behind"); behind != c.now.Before(unlock1000) {
|
|
t.Fatalf("warning of a clock behind: %v, in %q", behind, stderr)
|
|
}
|
|
official, _ := os.ReadFile(filepath.Join(releases, "1000.dkr"))
|
|
if got, _ := os.ReadFile(saved); !bytes.Equal(got, official) {
|
|
t.Fatalf("saved .dkr %x, want %x", got, official)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A release in hand that step 10 rejects gives the code of step 10, and one
|
|
// the archive lacks is ErrReleaseUnavailable; nothing is written.
|
|
func TestDecryptWithBadReleaseInHand(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write := func(name string, b []byte) string {
|
|
p := filepath.Join(dir, name)
|
|
if err := os.WriteFile(p, b, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
r1001, _ := os.ReadFile(filepath.Join(releases, "1001.dkr"))
|
|
r1000, _ := os.ReadFile(filepath.Join(releases, "1000.dkr"))
|
|
v2 := bytes.Clone(r1000)
|
|
v2[bytes.Index(v2, []byte{0x01, 0x01})+1] = 2
|
|
for _, c := range []struct {
|
|
name string
|
|
release string
|
|
capsule string
|
|
want *datekeys.Error
|
|
}{
|
|
{"another round", write("1001.dkr", r1001), "time_only", datekeys.ErrRoundMismatch},
|
|
{"version 2", write("v2.dkr", v2), "time_only", datekeys.ErrUnsupportedVersion},
|
|
{"a byte after it", write("long.dkr", append(bytes.Clone(r1000), 0)), "time_only", datekeys.ErrNonCanonicalCBOR},
|
|
{"a large file", write("large.dkr", append(bytes.Clone(r1000), make([]byte, 20000)...)), "time_only", datekeys.ErrNonCanonicalCBOR},
|
|
{"bad JSON", write("bad.json", []byte(`{"round":1000}`)), "time_only", datekeys.ErrReleaseInvalid},
|
|
{"a round the archive lacks", filepath.Join(releases, testkit.ArchiveFile), "format2_time_and_key_sixteen", datekeys.ErrReleaseUnavailable},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
out := filepath.Join(dir, c.name)
|
|
args := []string{"decrypt", "-in", filepath.Join(fixtures, c.capsule+".dkc"), "-out", out, "-release", c.release}
|
|
if c.capsule == "format2_time_and_key_sixteen" {
|
|
args = append(args, "-identity", filepath.Join(dir, "none"))
|
|
// The capsule needs a credential before the release: give it
|
|
// one that is never reached.
|
|
write("none", []byte(testkit.Stranger().String()+"\n"))
|
|
}
|
|
_, _, err := cli(t, later, args...)
|
|
if !errors.Is(err, c.want) {
|
|
t.Fatalf("got %v, want %v", err, c.want)
|
|
}
|
|
if _, err := os.Stat(out); !os.IsNotExist(err) {
|
|
t.Fatal("output written")
|
|
}
|
|
})
|
|
}
|
|
if _, _, err := cli(t, later, "decrypt", "-in", filepath.Join(fixtures, "time_only.dkc"), "-out", filepath.Join(dir, "x"), "-release", "a.dkr", "-relay", "http://127.0.0.1:1"); err == nil {
|
|
t.Fatal("-release and -relay together")
|
|
}
|
|
}
|
|
|
|
// A network source is still never asked before the round time.
|
|
func TestDecryptFromRelayBeforeTheRoundTime(t *testing.T) {
|
|
_, _, err := cli(t, unlock1000.Add(-time.Second), "decrypt", "-in", filepath.Join(fixtures, "time_only.dkc"), "-out", filepath.Join(t.TempDir(), "x"), "-relay", "http://127.0.0.1:1")
|
|
if !errors.Is(err, datekeys.ErrReleaseUnavailable) {
|
|
t.Fatalf("got %v", err)
|
|
}
|
|
}
|
|
|
|
// datekeys release fetches, verifies and saves the .dkr of a capsule
|
|
// without opening it, and never before the round time.
|
|
func TestReleaseCommand(t *testing.T) {
|
|
dir := t.TempDir()
|
|
url := relay(t)
|
|
out := filepath.Join(dir, "carta.dkr")
|
|
if _, _, err := cli(t, later, "release", "-in", filepath.Join(fixtures, "format3_time_and_key_portable.dkc"), "-out", out, "-relay", url); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
official, _ := os.ReadFile(filepath.Join(releases, "1000.dkr"))
|
|
if got, _ := os.ReadFile(out); !bytes.Equal(got, official) {
|
|
t.Fatalf("saved .dkr %x, want %x", got, official)
|
|
}
|
|
if _, _, err := cli(t, later, "release", "-in", filepath.Join(fixtures, "time_only.dkc"), "-out", out, "-relay", url); err == nil {
|
|
t.Fatal("an existing .dkr was overwritten")
|
|
}
|
|
_, _, err := cli(t, unlock1000.Add(-time.Second), "release", "-in", filepath.Join(fixtures, "time_only.dkc"), "-out", filepath.Join(dir, "early.dkr"), "-relay", "http://127.0.0.1:1")
|
|
if !errors.Is(err, datekeys.ErrReleaseUnavailable) {
|
|
t.Fatalf("before the round time: %v", err)
|
|
}
|
|
}
|