package capsule_test import ( "encoding/hex" "path/filepath" "reflect" "slices" "strings" "testing" "time" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/capsule" "g.activething.com/go/DateKeys/internal/testkit" ) // testdata/vectors/security_cms.json is frozen: each case is evaluated again, // with its context, and must give its verdicts, the result of each signer, // the authority and the time of a valid seal, and its lines, byte for byte // (spec v0.16 §29.7, §29.10, §29.11). func TestCMSVectors(t *testing.T) { var f testkit.CMSVectorFile if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "security_cms.json"), &f); err != nil { t.Fatal(err) } if f.Spec != datekeys.SpecVersion || !strings.Contains(f.Description, "v0.16") || len(f.Cases) < 140 { t.Fatalf("spec %q, %d cases: %s", f.Spec, len(f.Cases), f.Description) } seen := map[string]bool{} verdicts := map[string]bool{} for _, c := range f.Cases { if seen[c.Name] { t.Errorf("two cases named %q", c.Name) } seen[c.Name] = true verdicts[c.Signature] = true verdicts[c.Seal] = true t.Run(c.Name, func(t *testing.T) { area, err := hex.DecodeString(c.SecurityCBOR) if err != nil { t.Fatal(err) } var sc capsule.SecurityContext cc, err1 := hex.DecodeString(c.Context.ControlCommit) hd, err2 := hex.DecodeString(c.Context.HeadDigest) if err1 != nil || err2 != nil || len(cc) != 32 || len(hd) != 32 { t.Fatalf("the context: %+v", c.Context) } copy(sc.ControlCommit[:], cc) copy(sc.HeadDigest[:], hd) if sc.RoundTime, err = time.Parse(time.RFC3339, c.Context.RoundTime); err != nil { t.Fatal(err) } v := capsule.EvaluateSecurityIn(area, &sc) if string(v.Signature) != c.Signature || string(v.Seal) != c.Seal { t.Fatalf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.Signature, c.Seal) } var signers, foreign []testkit.FixtureSignerResult var holder, when, reason string if d := v.Detail; d != nil { signers, foreign = vectorSignerResults(d.Signers), vectorSignerResults(d.Foreign) if !d.SealTime.IsZero() { holder, when = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano) } reason = string(d.SealReason) } if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime || reason != c.SealReason { t.Errorf("signers %+v foreign %+v seal %q %q %q; want %+v %+v %q %q %q", signers, foreign, holder, when, reason, c.Signers, c.Foreign, c.SealHolder, c.SealTime, c.SealReason) } if lines := v.Lines(); !slices.Equal(lines, c.Lines) { t.Errorf("lines %q, want %q", lines, c.Lines) } // No line of the verdicts carries a control or a bidi character, // whatever the certificates say (spec §29.7). for _, line := range c.Lines { for _, r := range line { if r < 0x20 || r >= 0x7f && r <= 0x9f || r >= 0x202a && r <= 0x202e || r >= 0x2066 && r <= 0x2069 || r == 0xfeff { t.Errorf("a line with %U: %q", r, line) } } } }) } // Every verdict of alg 2 and of seal_type 2 has a case. for _, want := range []capsule.Verdict{ capsule.VerdictNoSignature, capsule.VerdictSignatureUnchecked, capsule.VerdictSignatureInvalid, capsule.VerdictSignedOther, capsule.VerdictSignedIncomplete, capsule.VerdictSignedComplete, capsule.VerdictNoSeal, capsule.VerdictSealUnsupported, capsule.VerdictSealUnreadable, capsule.VerdictSealInvalid, capsule.VerdictSealed, capsule.VerdictSealedLate, } { if !verdicts[string(want)] { t.Errorf("no case gives %s", want) } } } // vectorSignerResults are the results of the signers as security_cms.json // writes them: t with the fraction of its token when it has one. func vectorSignerResults(lines []capsule.SignerLine) []testkit.FixtureSignerResult { var out []testkit.FixtureSignerResult for _, l := range lines { r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)} if !l.SealTime.IsZero() { r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano) } out = append(out, r) } return out }