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/format2_test.go

562 lines
22 KiB

package capsule_test
import (
"bytes"
"context"
"encoding/binary"
"errors"
"math/rand/v2"
"runtime"
"strings"
"testing"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
)
// The tests of this file cover format 2 (spec v0.9): the changes of spec §76
// "Cambios normativos de la v0.9" and the tests it names.
// Spec §22, §23, §70: a reader opens every format and reports which; any
// other VERSION is rejected at step 2, without a request.
func TestFormatDispatch(t *testing.T) {
for _, name := range fixtureNames {
f := loadFixture(t, name)
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(f.dkc), f.openOptions(t))
if err != nil || opened.Format != f.format() || opened.Inspection.Prelude.Format != f.format() {
t.Fatalf("%s: format %d, %v", name, opened.Format, err)
}
}
f := loadFixture(t, "format2_time_only")
for _, v := range []byte{0, 4, 0x80, 0xff} {
dkc := bytes.Clone(f.dkc)
dkc[4] = v
step, calls, err := openStep(t, dkc, capsule.OpenOptions{Now: testkit.Fixed(f.unlock(t))})
expectStep(t, "VERSION "+string(rune('0'+v%10)), step, err, datekeys.ErrUnsupportedVersion, 2)
if calls != 0 {
t.Errorf("VERSION %d: %d release requests", v, calls)
}
}
// VERSION 3 is format 3 since v0.10: it passes step 2. Without a Sink,
// Open stops right there with ErrSinkRequired, a caller error with no code,
// no failed step and no request; with one, the version 2 control fails at
// step 14.
dkc := bytes.Clone(f.dkc)
dkc[4] = 3
src := testkit.NewSource(f.release)
opened, err := capsule.Open(context.Background(), &bytes.Buffer{}, bytes.NewReader(dkc),
capsule.OpenOptions{Registry: testkit.Registry(), Source: src, Now: testkit.Fixed(f.unlock(t))})
if !errors.Is(err, capsule.ErrSinkRequired) || datekeys.Code(err) != "" || src.Calls != 0 || opened.Format != capsule.Format3 {
t.Errorf("VERSION 3 without a Sink: %v, code %q, %d requests, format %d", err, datekeys.Code(err), src.Calls, opened.Format)
}
if c := opened.Inspection.Checks; len(c) != 2 || !c[0].OK || !c[1].OK || c[1].Step != 2 {
t.Errorf("VERSION 3 without a Sink: checks %+v", c)
}
step, calls, err := openStep(t, dkc, capsule.OpenOptions{Now: testkit.Fixed(f.unlock(t)), Sink: testkit.DiscardSink{}})
expectStep(t, "VERSION 3 with a Sink", step, err, datekeys.ErrUnsupportedVersion, 14)
if calls != 1 {
t.Errorf("VERSION 3 with a Sink: %d release requests", calls)
}
// A capsule of format 1 or 2 needs dst, whatever the Sink.
src = testkit.NewSource(f.release)
o := f.openOptions(t)
o.Source, o.Sink = src, testkit.DiscardSink{}
if _, err := capsule.Open(context.Background(), nil, bytes.NewReader(f.dkc), o); err == nil || datekeys.Code(err) != "" || src.Calls != 0 {
t.Errorf("format 2 without dst: %v, %d requests", err, src.Calls)
}
}
// Spec §22, §76 change 1: VERSION is public and anyone can edit it. A capsule
// relabeled to another format fails at step 12 or 14, after the release,
// and nothing is written.
func TestFormatRelabel(t *testing.T) {
relabel := func(dkc []byte, v byte) []byte {
c := bytes.Clone(dkc)
c[4] = v
return c
}
identity := func(f *fixture) []age.Identity { return f.credentials(t)[:1] }
to1, tk1 := loadFixture(t, "time_only"), loadFixture(t, "time_and_key_portable")
to2, tk2 := loadFixture(t, "format2_time_only"), loadFixture(t, "format2_time_and_key_portable")
// A format 1 capsule with exactly 16 stanzas, as only a generator of test
// vectors writes it: the stanza count passes step 12 of format 2, and the
// version of its control fails step 14.
stranger := testkit.Stranger()
sixteen := []age.Recipient{stranger.Recipient()}
for range 15 {
id, _ := age.GenerateX25519Identity()
sixteen = append(sixteen, id.Recipient())
}
b, err := testkit.Build{Format: capsule.Format1, Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey,
AccessRecipients: sixteen, Plaintext: []byte("sixteen")}.Make()
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
dkc []byte
ids []age.Identity
now *fixture
want error
step int
}{
{"format 2 time_only relabeled 1", relabel(to2.dkc, 1), nil, to2, datekeys.ErrUnsupportedVersion, 14},
{"format 2 time_and_key relabeled 1", relabel(tk2.dkc, 1), identity(tk2), tk2, datekeys.ErrUnsupportedVersion, 14},
{"format 1 time_only relabeled 2", relabel(to1.dkc, 2), nil, to1, datekeys.ErrUnsupportedVersion, 14},
{"format 1 time_and_key with one stanza relabeled 2", relabel(tk1.dkc, 2), identity(tk1), tk1, datekeys.ErrPolicyStructureMismatch, 12},
{"format 1 time_and_key with 16 stanzas relabeled 2", relabel(b.DKC, 2), []age.Identity{stranger}, nil, datekeys.ErrUnsupportedVersion, 14},
// Format 3 has the 16 stanzas and the padding of format 2 (spec §29.1).
{"format 2 time_only relabeled 3", relabel(to2.dkc, 3), nil, to2, datekeys.ErrUnsupportedVersion, 14},
{"format 2 time_and_key relabeled 3", relabel(tk2.dkc, 3), identity(tk2), tk2, datekeys.ErrUnsupportedVersion, 14},
{"format 1 time_only relabeled 3", relabel(to1.dkc, 3), nil, to1, datekeys.ErrUnsupportedVersion, 14},
{"format 1 time_and_key with one stanza relabeled 3", relabel(tk1.dkc, 3), identity(tk1), tk1, datekeys.ErrPolicyStructureMismatch, 12},
{"format 1 time_and_key with 16 stanzas relabeled 3", relabel(b.DKC, 3), []age.Identity{stranger}, nil, datekeys.ErrUnsupportedVersion, 14},
} {
// The Sink plays no part in formats 1 and 2.
o := capsule.OpenOptions{Identities: tc.ids, Sink: testkit.DiscardSink{}}
if tc.now != nil {
o.Now = testkit.Fixed(tc.now.unlock(t))
}
step, _, err := openStep(t, tc.dkc, o)
expectStep(t, tc.name, step, err, tc.want, tc.step)
}
// Unrelabeled, the format 1 capsule with 16 stanzas opens.
if got, err := open(t, b.DKC, capsule.OpenOptions{Registry: testkit.Registry(), Source: testkit.NewSource(testkit.Release(1000)),
Now: testkit.Fixed(testkit.Genesis().AddDate(1, 0, 0)), Identities: []age.Identity{stranger}}); err != nil || string(got) != "sixteen" {
t.Fatalf("format 1 with 16 stanzas: %v", err)
}
}
// Spec §33, §70: format 1 keeps the rules of v0.8.2, one or more stanzas and
// no padding, and format 2 requires exactly 16 stanzas.
func TestFormat1Compatibility(t *testing.T) {
stranger := testkit.Stranger()
recipients := []age.Recipient{stranger.Recipient()}
for range 16 {
id, _ := age.GenerateX25519Identity()
recipients = append(recipients, id.Recipient())
}
o := capsule.OpenOptions{Identities: []age.Identity{stranger}}
for _, tc := range []struct {
name string
b testkit.Build
want error
step int
output string
}{
{"format 1 with 17 stanzas", testkit.Build{Format: capsule.Format1, Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey, AccessRecipients: recipients}, nil, 0, "malicious creator"},
{"format 1 with one stanza", testkit.Build{Format: capsule.Format1, Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey, AccessRecipients: recipients[:1]}, nil, 0, "malicious creator"},
{"format 2 with 17 stanzas", testkit.Build{Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey, AccessRecipients: recipients, Slots: 17}, datekeys.ErrPolicyStructureMismatch, 12, ""},
{"format 2 with one stanza", testkit.Build{Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey, AccessRecipients: recipients[:1], Slots: 1}, datekeys.ErrPolicyStructureMismatch, 12, ""},
{"format 2 with 16 stanzas", testkit.Build{Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey, AccessRecipients: recipients[:1]}, nil, 0, "malicious creator"},
} {
dkc, _ := build(t, tc.b)
step, _, err := openStep(t, dkc, o)
if tc.want == nil {
if err != nil {
t.Errorf("%s: %v", tc.name, err)
}
continue
}
expectStep(t, tc.name, step, err, tc.want, tc.step)
}
// The plaintext of a format 1 PAYLOAD_AGE is the content, without padding.
b, err := testkit.Build{Format: capsule.Format1, Plaintext: []byte("x")}.Make()
if err != nil {
t.Fatal(err)
}
if len(b.Payload) != int(capsule.PayloadAgeLength(1)) {
t.Fatalf("format 1 PAYLOAD_AGE of %d bytes", len(b.Payload))
}
}
// Spec §29.1: the two rules, exact on integers, their properties and their
// limits, and the length of PAYLOAD_AGE they give.
func TestPaddingRules(t *testing.T) {
check := func(l uint64) {
b, err1 := capsule.PaddedLength(l, capsule.Bloque256)
r, err2 := capsule.PaddedLength(l, capsule.Reforzado)
if err := errors.Join(err1, err2); err != nil {
t.Fatalf("L = %d: %v", l, err)
}
for _, p := range []uint64{b, r} {
if p%256 != 0 || p < 256 || p < l {
t.Fatalf("L = %d: P = %d", l, p)
}
}
if l > 256 && b-l >= 256 {
t.Fatalf("L = %d: bloque256 adds %d bytes", l, b-l)
}
if l <= 8192 && b != r {
t.Fatalf("L = %d: bloque256 %d, reforzado %d", l, b, r)
}
if r < b {
t.Fatalf("L = %d: reforzado %d below bloque256 %d", l, r, b)
}
// Above 8192, reforzado adds less than L / 2^S.
if e := uint64(63 - clz(l)); l > 8192 && r-l >= l>>bitlen(e) {
t.Fatalf("L = %d: reforzado adds %d bytes", l, r-l)
}
}
for l := range uint64(20000) {
check(l)
}
rng := rand.New(rand.NewPCG(20260929, 1))
for range 20000 {
check(rng.Uint64N(capsule.MaxPayloadLength + 1))
}
check(capsule.MaxPayloadLength)
if b, _ := capsule.PaddedLength(8193, capsule.Bloque256); b != 8448 {
t.Fatalf("bloque256(8193) = %d", b)
}
if r, _ := capsule.PaddedLength(8193, capsule.Reforzado); r != 8704 {
t.Fatalf("reforzado(8193) = %d", r)
}
for _, tc := range []struct {
l uint64
p capsule.Padding
}{{0, 0}, {0, 3}, {capsule.MaxPayloadLength + 1, capsule.Bloque256}, {capsule.MaxPayloadLength + 1, capsule.Reforzado}, {1 << 63, capsule.Reforzado}} {
if _, err := capsule.PaddedLength(tc.l, tc.p); err == nil {
t.Errorf("L = %d, code %d accepted", tc.l, tc.p)
}
}
if capsule.Bloque256.String() != "bloque256" || capsule.Reforzado.String() != "reforzado" || capsule.Padding(9).String() != "padding(9)" {
t.Fatal("padding names")
}
// PAYLOAD_AGE: 184 + P + 16·max(1, ⌈P / 65536⌉), checked against age.
id, _ := age.GenerateX25519Identity()
for _, p := range []uint64{0, 256, 65536, 65536 + 256, 2 * 65536, 79872} {
file, _, err := testkit.Encrypt(make([]byte, p), id.Recipient())
if err != nil {
t.Fatal(err)
}
if uint64(len(file)) != capsule.PayloadAgeLength(p) {
t.Fatalf("P = %d: age writes %d bytes, the formula gives %d", p, len(file), capsule.PayloadAgeLength(p))
}
}
}
func clz(x uint64) int {
n := 0
for i := 63; i >= 0 && x&(1<<i) == 0; i-- {
n++
}
return n
}
func bitlen(x uint64) uint64 { return uint64(64 - clz(x)) }
// Spec §29.1, §56, §63 step 17: the reader checks the length and the padding
// of the plaintext, whatever the moment it finds a fault, writes the content
// only and never the padding.
func TestPaddingChecksAtStep17(t *testing.T) {
f, err := testkit.LoadFixture(fixtureDir, "format2_time_only_extensions")
if err != nil {
t.Fatal(err)
}
fx := loadFixture(t, "format2_time_only_extensions")
l, p := int(f.PayloadLength), int(f.PaddedLength)
padded := func() []byte { return append(bytes.Clone(fx.plaintext), make([]byte, p-l)...) }
set := func(i int, v byte) []byte { b := padded(); b[i] = v; return b }
for _, tc := range []struct {
name string
plaintext []byte
ok bool
}{
{"the content and its padding, sealed again", padded(), true},
{"first padding byte not zero", set(l, 0x01), false},
{"a padding byte not zero", set(100, 0x80), false},
{"last padding byte not zero", set(p-1, 0x01), false},
{"P - 1 bytes", padded()[:p-1], false},
{"P + 1 bytes", append(padded(), 0), false},
{"2P bytes", append(padded(), make([]byte, p)...), false},
{"L bytes, without padding", padded()[:l], false},
{"no plaintext", nil, false},
} {
in, err := f.WithPayloadPlaintext(tc.plaintext)
if err != nil {
t.Fatal(err)
}
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(in.DKC), fx.openOptions(t))
if tc.ok {
if err != nil || !bytes.Equal(out.Bytes(), fx.plaintext) || opened.PayloadLength != uint64(l) {
t.Errorf("%s: %v", tc.name, err)
}
continue
}
expectStep(t, tc.name, failedStep(t, opened.Inspection.Checks, err), err, datekeys.ErrIntegrity, 17)
if out.Len() > l || !bytes.HasPrefix(fx.plaintext, out.Bytes()) {
t.Errorf("%s: %d bytes written, not a prefix of the content", tc.name, out.Len())
}
}
}
// Spec §67: a padding that spans several STREAM chunks, generated at run
// time rather than frozen in a 5 MB fixture: L = 5 000 000 with code 2 gives
// P = 5 111 808, more than a chunk of zeros after the content.
func TestPaddingAcrossChunks(t *testing.T) {
content := bytes.Repeat([]byte("0123456789"), 500000)
b, err := testkit.Build{Plaintext: content, Padding: capsule.Reforzado}.Make()
if err != nil {
t.Fatal(err)
}
o := capsule.OpenOptions{Registry: testkit.Registry(), Source: testkit.NewSource(testkit.Release(1000)), Now: testkit.Fixed(testkit.Genesis().AddDate(1, 0, 0))}
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(b.DKC), o)
if err != nil || !bytes.Equal(out.Bytes(), content) || opened.PaddedLength != 5111808 {
t.Fatalf("P = %d: %v", opened.PaddedLength, err)
}
// The last byte of the padding, in the last chunk, not zero.
id, err := agewrap.NewPayloadIdentity(b.PayloadIdentity)
if err != nil {
t.Fatal(err)
}
stanzas, err := agewrap.Stanzas(bytes.NewReader(b.Payload))
if err != nil {
t.Fatal(err)
}
fk, err := id.Unwrap(stanzas)
if err != nil {
t.Fatal(err)
}
plaintext := append(bytes.Clone(content), make([]byte, 5111808-len(content))...)
plaintext[len(plaintext)-1] = 1
payload, err := testkit.ResealAge(b.Payload, fk, plaintext)
if err != nil {
t.Fatal(err)
}
out.Reset()
opened, err = capsule.Open(context.Background(), &out, bytes.NewReader(testkit.Join(b.Prelude[:], b.PublicHeader, b.Sealed, payload)), o)
expectStep(t, "last padding byte not zero, 17 chunks away", failedStep(t, opened.Inspection.Checks, err), err, datekeys.ErrIntegrity, 17)
if !bytes.Equal(out.Bytes(), content) {
t.Fatalf("%d bytes written, want the content only", out.Len())
}
}
// Spec §31, §57, §76 change 6: L and P are not frame lengths. A time_only
// control that anyone seals can declare L = L_MAX over a PAYLOAD_AGE of 456
// bytes; the reader reserves nothing according to it and fails at step 17.
func TestDeclaredLengthIsNotAllocated(t *testing.T) {
f, err := testkit.LoadFixture(fixtureDir, "format2_empty_payload")
if err != nil {
t.Fatal(err)
}
in, err := f.WithControl(func(m map[uint64]any) { m[6] = payloadLength(capsule.MaxPayloadLength) })
if err != nil {
t.Fatal(err)
}
fx := loadFixture(t, "format2_empty_payload")
var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(in.DKC), fx.openOptions(t))
runtime.ReadMemStats(&after)
expectStep(t, "L = L_MAX over 456 bytes", failedStep(t, opened.Inspection.Checks, err), err, datekeys.ErrIntegrity, 17)
if grown := after.TotalAlloc - before.TotalAlloc; grown > 16<<20 {
t.Fatalf("%d bytes allocated", grown)
}
}
// Spec §62.1 rule 7 and its note, §76 change 8: SEALED_CONTROL_LEN is the
// exact length of SEALED_CONTROL, and follows the formulas of the note.
func TestSealedControlLength(t *testing.T) {
c := func(n int) int { return max(1, (n+65535)/65536) }
ext, _ := extension.New("org.example.note", 2, bytes.Repeat([]byte{7}, 100))
for _, tc := range []struct {
name string
setup func(o *capsule.EncryptOptions)
want func(control int) int
}{
{"time_only", func(*capsule.EncryptOptions) {}, func(n int) int { return 335 + 4 + n + 16*c(n) }},
{"time_and_key", func(o *capsule.EncryptOptions) { o.Policy, o.NewPortableKey = capsule.TimeAndKey, true }, func(n int) int {
inner := 86 + 98*16 + n + 16*c(n)
return 335 + 4 + inner + 16*c(inner)
}},
} {
for _, exts := range [][]extension.Extension{nil, {ext}} {
opts := past(t, 1000)
opts.ControlNoncritical = exts
tc.setup(&opts)
var dkc bytes.Buffer
if _, err := encrypt(t, &dkc, "length", opts); err != nil {
t.Fatal(err)
}
control, err := capsule.EncodeControl(&capsule.Control{Noncritical: exts, Padding: capsule.Reforzado}, capsule.Format2)
if err != nil {
t.Fatal(err)
}
got := int(binary.BigEndian.Uint32(dkc.Bytes()[12:16]))
p, err := testkit.Split(dkc.Bytes())
if err != nil {
t.Fatal(err)
}
if got != tc.want(len(control)) || got != len(p.Sealed) {
t.Errorf("%s, %d control extensions: SEALED_CONTROL_LEN %d, formula %d", tc.name, len(exts), got, tc.want(len(control)))
}
if len(exts) == 0 && ((tc.name == "time_only" && got != 458) || (tc.name == "time_and_key" && got != 2128)) {
t.Errorf("%s: %d, spec §62.1 gives 458 and 2128", tc.name, got)
}
}
}
}
// Spec §29, §62.1 rule 5, §76 change 9: I_PAYLOAD is new for every capsule,
// so that opening one capsule opens no other payload.
func TestPayloadIdentityReuse(t *testing.T) {
p := profile.Quicknet()
var controls [2]*capsule.Control
var parts [2]testkit.Parts
for i := range 2 {
var dkc bytes.Buffer
if _, err := encrypt(t, &dkc, "same content", past(t, 1000)); err != nil {
t.Fatal(err)
}
var err error
if parts[i], err = testkit.Split(dkc.Bytes()); err != nil {
t.Fatal(err)
}
id, _ := agewrap.NewTimeIdentity(p, 1000, testkit.Release(1000))
if controls[i], err = capsule.DecodeControl(decryptAge(t, parts[i].Sealed, id), capsule.Format2); err != nil {
t.Fatal(err)
}
}
if controls[0].PayloadIdentity == controls[1].PayloadIdentity {
t.Fatal("two capsules share I_PAYLOAD")
}
a, b := parts[0], parts[1]
step, _, err := openStep(t, testkit.Join(a.Prelude, a.Header, a.Sealed, b.Payload), capsule.OpenOptions{})
expectStep(t, "SEALED_CONTROL_A + PAYLOAD_AGE_B", step, err, datekeys.ErrIntegrity, 17)
}
// Spec §39, §76 change 2: INNER_ACCESS_AGE holds exactly 16 X25519 stanzas
// with distinct shares, whatever the number of credentials; each credential
// opens exactly one, and no credential opens the others.
func TestInnerHasSixteenStanzas(t *testing.T) {
for _, n := range []int{0, 2, 15} {
opts := past(t, 1000)
opts.Policy, opts.NewPortableKey = capsule.TimeAndKey, true
var ids []age.Identity
for range n {
id, _ := age.GenerateX25519Identity()
opts.Recipients = append(opts.Recipients, id.Recipient())
ids = append(ids, id)
}
var dkc bytes.Buffer
res, err := encrypt(t, &dkc, "slots", opts)
if err != nil {
t.Fatal(err)
}
k, _ := res.PortableKey.Identity()
ids = append(ids, k)
p, _ := testkit.Split(dkc.Bytes())
timeID, _ := agewrap.NewTimeIdentity(profile.Quicknet(), 1000, testkit.Release(1000))
st, err := agewrap.Stanzas(bytes.NewReader(decryptAge(t, p.Sealed, timeID)))
if err != nil {
t.Fatal(err)
}
if err := agewrap.CheckAccessStanzas(st, agewrap.AccessSlots); err != nil {
t.Fatalf("%d credentials: %v", len(ids), err)
}
opened := map[int]bool{}
for _, id := range ids {
for i, s := range st {
if _, err := id.Unwrap([]*age.Stanza{s}); err == nil {
if opened[i] {
t.Fatalf("stanza %d opened twice", i)
}
opened[i] = true
}
}
}
if len(opened) != len(ids) {
t.Fatalf("%d credentials open %d stanzas", len(ids), len(opened))
}
}
}
// Spec §39: a dummy wraps FK_ACCESS like a credential, so whoever kept the
// private key of a dummy would open the capsule: the writer MUST NOT keep it.
// Encrypt returns no key but the portable one, and its dummies are fresh: no
// ephemeral share repeats across capsules for the same credentials.
func TestDummyRecipients(t *testing.T) {
kept, _ := age.GenerateX25519Identity()
b, err := testkit.Build{Declared: capsule.TimeAndKey, Structure: capsule.TimeAndKey,
AccessRecipients: []age.Recipient{testkit.Stranger().Recipient(), kept.Recipient()}}.Make()
if err != nil {
t.Fatal(err)
}
o := capsule.OpenOptions{Identities: []age.Identity{kept}}
if step, _, err := openStep(t, b.DKC, o); err != nil {
t.Fatalf("the kept key does not open the capsule: step %d, %v", step, err)
}
holder, _ := age.GenerateX25519Identity()
shares := map[string]bool{}
for range 2 {
opts := past(t, 1000)
opts.Policy, opts.Recipients = capsule.TimeAndKey, []age.Recipient{holder.Recipient()}
var dkc bytes.Buffer
res, err := encrypt(t, &dkc, "dummies", opts)
if err != nil || res.PortableKey != nil {
t.Fatalf("result %+v, %v", res, err)
}
p, _ := testkit.Split(dkc.Bytes())
timeID, _ := agewrap.NewTimeIdentity(profile.Quicknet(), 1000, testkit.Release(1000))
st, _ := agewrap.Stanzas(bytes.NewReader(decryptAge(t, p.Sealed, timeID)))
for _, s := range st {
if shares[s.Args[0]] {
t.Fatal("an ephemeral share repeats across capsules")
}
shares[s.Args[0]] = true
}
}
if len(shares) != 2*agewrap.AccessSlots {
t.Fatalf("%d shares", len(shares))
}
}
// The writer checks the format 2 rules it must follow through the reader:
// every capsule it writes opens with the rules of the reader, for both codes
// and both policies, whatever the content length.
func TestEncryptWritesFormat2(t *testing.T) {
for _, l := range []int{0, 1, 255, 256, 257, 8193, 65536, 65537} {
for _, code := range []capsule.Padding{capsule.Bloque256, capsule.Reforzado} {
content := strings.Repeat("d", l)
opts := past(t, 1000)
opts.Padding = code
var dkc bytes.Buffer
res, err := encrypt(t, &dkc, content, opts)
if err != nil {
t.Fatal(err)
}
want, _ := capsule.PaddedLength(uint64(l), code)
if dkc.Bytes()[4] != 2 || res.PaddedLength != want {
t.Fatalf("L = %d, %s: VERSION %d, P = %d", l, code, dkc.Bytes()[4], res.PaddedLength)
}
p, _ := testkit.Split(dkc.Bytes())
if uint64(len(p.Payload)) != capsule.PayloadAgeLength(want) {
t.Fatalf("L = %d, %s: PAYLOAD_AGE of %d bytes", l, code, len(p.Payload))
}
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(dkc.Bytes()), defaultOpen(1000))
if err != nil || out.String() != content || opened.Padding != code || opened.PaddedLength != want {
t.Fatalf("L = %d, %s: %v", l, code, err)
}
}
}
}

Powered by TurnKey Linux.