The reader evaluates a signature of alg 2 (F1, F2, F5, F6, with the signers named) and a seal of seal_type 2 (S1 to S5) in the context of the capsule, with SIGNERS in strictly ascending order of certificate hashes. The writer takes a CMSSigner, which gets AUTHOR_MESSAGE and returns what the person signed outside, and a Sealer, which asks an authority for the token over SEAL_SUBJECT; it checks the result with the rules of the reader and writes nothing unless every required signer is valid and sealed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>v0.11
parent
55a261c792
commit
6cac49b5d8
@ -0,0 +1,215 @@
|
||||
package capsule
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"g.activething.com/go/DateKeys/codec"
|
||||
"g.activething.com/go/DateKeys/internal/cms"
|
||||
"g.activething.com/go/DateKeys/internal/pathrule"
|
||||
)
|
||||
|
||||
// SealTypeRFC3161 is the seal_type of a seal that is an RFC 3161 time-stamp
|
||||
// token (spec v0.11, §29.3, §29.11).
|
||||
const SealTypeRFC3161 = 2
|
||||
|
||||
// MaxSigners is the most required signers of an alg 2 signature (§29.10).
|
||||
const MaxSigners = 16
|
||||
|
||||
// EncodeSigners returns SIGNERS, the content of key 1 of an author-signature
|
||||
// of alg 2: a CBOR array of 1 to 16 strings of 32 bytes, the SHA-256 of the
|
||||
// certificate of each required signer, in strictly ascending order of bytes
|
||||
// (spec §29.10). It sorts them, and fails when there are none, too many or
|
||||
// two are equal.
|
||||
func EncodeSigners(hashes [][32]byte) ([]byte, error) {
|
||||
if len(hashes) < 1 || len(hashes) > MaxSigners {
|
||||
return nil, errors.New("capsule: SIGNERS holds from 1 to 16 certificates")
|
||||
}
|
||||
sorted := append([][32]byte(nil), hashes...)
|
||||
for i := 1; i < len(sorted); i++ { // insertion sort: at most 16
|
||||
for j := i; j > 0 && bytes.Compare(sorted[j-1][:], sorted[j][:]) > 0; j-- {
|
||||
sorted[j-1], sorted[j] = sorted[j], sorted[j-1]
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(sorted); i++ {
|
||||
if sorted[i-1] == sorted[i] {
|
||||
return nil, errors.New("capsule: SIGNERS names a certificate twice")
|
||||
}
|
||||
}
|
||||
var e codec.Encoder
|
||||
e.Array(len(sorted))
|
||||
for _, h := range sorted {
|
||||
e.Bstr(h[:])
|
||||
}
|
||||
return e.Out()
|
||||
}
|
||||
|
||||
// decodeSigners reads SIGNERS and checks the profile of spec §29.10.
|
||||
func decodeSigners(b []byte) ([][32]byte, error) {
|
||||
var out [][32]byte
|
||||
decode := func(d *codec.Decoder) error {
|
||||
n, err := d.Array(MaxSigners)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < 1 {
|
||||
return errors.New("SIGNERS is empty")
|
||||
}
|
||||
for range n {
|
||||
h, err := d.Bstr(32, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var x [32]byte
|
||||
copy(x[:], h)
|
||||
if len(out) > 0 && bytes.Compare(out[len(out)-1][:], x[:]) >= 0 {
|
||||
return errors.New("SIGNERS is not in strictly ascending order")
|
||||
}
|
||||
out = append(out, x)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
encode := func(e *codec.Encoder) {
|
||||
e.Array(len(out))
|
||||
for _, h := range out {
|
||||
e.Bstr(h[:])
|
||||
}
|
||||
}
|
||||
if err := codec.Unmarshal(b, decode, encode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// holderText is how §29.7 shows a name: the name, when it meets the rules of
|
||||
// the declared author, and the SHA-256 of the certificate otherwise.
|
||||
func holderText(name string, hash [32]byte) string {
|
||||
if name != "" && utf8.ValidString(name) && len(name) <= MaxAuthorLen && pathrule.CheckAuthor(name) == nil {
|
||||
return name
|
||||
}
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// evaluateCMS sets the verdict of a signature of alg 2 (spec §29.10): F1 for
|
||||
// content that breaks its profile, F2 when the signature of a required
|
||||
// signer is invalid, F5 when something the capsule demands is missing, F6
|
||||
// when every required signer is valid and sealed. hasSeal is whether key 3
|
||||
// exists, which an alg 2 signature forbids.
|
||||
func evaluateCMS(v *Verdicts, a *authorSignature, hasSeal bool, c *SecurityContext) {
|
||||
required, err := decodeSigners(a.key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sd, err := cms.ParseSignature(a.value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg := AuthorMessage(c.ControlCommit, c.HeadDigest, SignersDigest(AlgCMS, a.key))
|
||||
detail := &Detail{}
|
||||
byHash := map[[32]byte]*cms.SignerInfo{}
|
||||
for _, s := range sd.Signers {
|
||||
byHash[s.Cert.Hash] = s
|
||||
}
|
||||
invalid, incomplete := false, hasSeal
|
||||
for _, h := range required {
|
||||
s := byHash[h]
|
||||
if s == nil {
|
||||
detail.Signers = append(detail.Signers, SignerLine{Holder: hex.EncodeToString(h[:]), Result: "absent"})
|
||||
incomplete = true
|
||||
continue
|
||||
}
|
||||
line := signerLine(s, msg, c.RoundTime)
|
||||
switch line.Result {
|
||||
case "invalid":
|
||||
invalid = true
|
||||
case "valid":
|
||||
default:
|
||||
incomplete = true
|
||||
}
|
||||
detail.Signers = append(detail.Signers, line)
|
||||
}
|
||||
for _, s := range sd.Signers {
|
||||
if !isRequired(required, s.Cert.Hash) {
|
||||
detail.Foreign = append(detail.Foreign, signerLine(s, msg, c.RoundTime))
|
||||
}
|
||||
}
|
||||
v.Detail = detail
|
||||
switch {
|
||||
case invalid:
|
||||
v.Signature = VerdictSignatureInvalid
|
||||
case incomplete:
|
||||
v.Signature = VerdictSignedIncomplete
|
||||
default:
|
||||
v.Signature = VerdictSignedComplete
|
||||
}
|
||||
}
|
||||
|
||||
func isRequired(required [][32]byte, h [32]byte) bool {
|
||||
for _, r := range required {
|
||||
if r == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// signerLine checks one SignerInfo as §29.10 orders: not verifiable, invalid,
|
||||
// without seal, with an invalid seal, out of validity, or valid.
|
||||
func signerLine(s *cms.SignerInfo, msg []byte, roundTime time.Time) SignerLine {
|
||||
l := SignerLine{Holder: holderText(s.Cert.Holder(), s.Cert.Hash), Issuer: s.Cert.IssuerName()}
|
||||
switch s.Check(msg) {
|
||||
case cms.NotVerifiable:
|
||||
l.Result = "not verifiable"
|
||||
return l
|
||||
case cms.Invalid:
|
||||
l.Result = "invalid"
|
||||
return l
|
||||
}
|
||||
if s.Token == nil {
|
||||
l.Result = "without seal"
|
||||
return l
|
||||
}
|
||||
tok, err := cms.ParseToken(s.Token)
|
||||
if err != nil || !tok.Check(s.Signature) {
|
||||
l.Result = "invalid seal"
|
||||
return l
|
||||
}
|
||||
if !s.Cert.ValidAt(tok.GenTime) {
|
||||
l.Result = "out of validity"
|
||||
return l
|
||||
}
|
||||
l.Result, l.SealTime = "valid", tok.GenTime
|
||||
l.Before = !roundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(roundTime)
|
||||
return l
|
||||
}
|
||||
|
||||
// evaluateSeal sets the verdict of a seal of seal_type 2 (spec §29.11): S2 or
|
||||
// S1 for the form and the algorithms, S3 when it does not verify, and S4 or
|
||||
// S5 when it does. signature is the content of key 2, nil without it.
|
||||
func evaluateSeal(v *Verdicts, s *seal, signature []byte, c *SecurityContext) {
|
||||
tok, err := cms.ParseToken(s.token)
|
||||
switch {
|
||||
case errors.Is(err, cms.ErrForm):
|
||||
v.Seal = VerdictSealUnreadable
|
||||
return
|
||||
case err != nil || !tok.ImprintIsSHA256():
|
||||
v.Seal = VerdictSealUnsupported
|
||||
return
|
||||
}
|
||||
subject := SealSubject(c.ControlCommit, c.HeadDigest, SigPart(signature))
|
||||
if !tok.Check(subject[:]) {
|
||||
v.Seal = VerdictSealInvalid
|
||||
return
|
||||
}
|
||||
if v.Detail == nil {
|
||||
v.Detail = &Detail{}
|
||||
}
|
||||
v.Detail.SealHolder, v.Detail.SealTime = holderText(tok.TSA.Holder(), tok.TSA.Hash), tok.GenTime
|
||||
v.Seal = VerdictSealedLate
|
||||
if !c.RoundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) {
|
||||
v.Seal = VerdictSealed
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,227 @@
|
||||
package capsule_test
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha256"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.activething.com/go/DateKeys/authorkey"
|
||||
"g.activething.com/go/DateKeys/capsule"
|
||||
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||
)
|
||||
|
||||
var (
|
||||
certFrom = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
certTo = time.Date(2032, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
roundTime = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
signedAt = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
func testContext() *capsule.SecurityContext {
|
||||
c := &capsule.SecurityContext{RoundTime: roundTime}
|
||||
c.ControlCommit[0], c.HeadDigest[0] = 1, 2
|
||||
return c
|
||||
}
|
||||
|
||||
// cmsArea builds the SECURITY_CBOR of a capsule with an alg 2 signature by
|
||||
// the signers, who all must sign, sealing each signature with tsa at when.
|
||||
func cmsArea(t *testing.T, c *capsule.SecurityContext, required []cmstest.Signer, signers []cmstest.Signer, tsa cmstest.Signer, when time.Time, seal []byte) []byte {
|
||||
t.Helper()
|
||||
var hashes [][32]byte
|
||||
for _, s := range required {
|
||||
hashes = append(hashes, sha256Sum(s.Cert.Raw))
|
||||
}
|
||||
list, err := capsule.EncodeSigners(hashes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, list))
|
||||
opts := cmstest.Options{}
|
||||
if tsa.Key != nil {
|
||||
opts.Token = func(sig []byte) []byte {
|
||||
return cmstest.Token(sig, when, cmstest.TokenOptions{Accuracy: time.Second}, tsa)
|
||||
}
|
||||
}
|
||||
content, err := capsule.EncodeAuthorSignature(capsule.AlgCMS, list, cmstest.Signature(msg, opts, signers...))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
area, err := capsule.EncodeSecurityWith(content, seal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return area
|
||||
}
|
||||
|
||||
// Spec v0.11 §29.7, §29.10: alg 2 gives F6 when every required signer is
|
||||
// valid and sealed, and the first of F2, F5 and F1 that applies otherwise.
|
||||
func TestEvaluateCMS(t *testing.T) {
|
||||
ana := cmstest.NewECDSA("Ana López", elliptic.P256(), certFrom, certTo)
|
||||
luis := cmstest.NewRSA("Luis Gómez", 2048, certFrom, certTo)
|
||||
otro := cmstest.NewECDSA("Otro", elliptic.P384(), certFrom, certTo)
|
||||
tsa := cmstest.NewECDSA("TSA de prueba", elliptic.P256(), certFrom, certTo)
|
||||
c := testContext()
|
||||
|
||||
// A co-signature, each with its seal: F6, with their names.
|
||||
v := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{ana, luis}, []cmstest.Signer{ana, luis}, tsa, signedAt, nil), c)
|
||||
if v.Signature != capsule.VerdictSignedComplete || v.Seal != capsule.VerdictNoSeal || v.Detail == nil || len(v.Detail.Signers) != 2 {
|
||||
t.Fatalf("a complete co-signature: %+v", v)
|
||||
}
|
||||
lines := v.Lines()
|
||||
if !strings.HasPrefix(lines[0], "Firmado con un certificado a nombre de ") || !strings.Contains(lines[0], "Ana López") || !strings.Contains(lines[0], "Luis Gómez") ||
|
||||
len(lines) != 3 || !strings.Contains(lines[1], "antes de la fecha de apertura") || strings.Contains(lines[1], "no antes") {
|
||||
t.Errorf("lines %q", lines)
|
||||
}
|
||||
|
||||
// A seal after the round time proves nothing before it.
|
||||
late := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, roundTime.Add(time.Hour), nil), c)
|
||||
if late.Signature != capsule.VerdictSignedComplete || !strings.Contains(late.Lines()[1], "no antes de la fecha de apertura") {
|
||||
t.Errorf("a late seal: %+v %q", late, late.Lines())
|
||||
}
|
||||
|
||||
// A signer who is not required shows apart and does not count.
|
||||
f := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana, otro}, tsa, signedAt, nil), c)
|
||||
if f.Signature != capsule.VerdictSignedComplete || len(f.Detail.Foreign) != 1 || f.Detail.Foreign[0].Holder != "Otro" || !strings.Contains(f.Lines()[len(f.Lines())-1], "No cuenta") {
|
||||
t.Errorf("a foreign signer: %+v %q", f, f.Lines())
|
||||
}
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
area []byte
|
||||
want capsule.Verdict
|
||||
}{
|
||||
"a required signer is absent": {cmsArea(t, c, []cmstest.Signer{ana, luis}, []cmstest.Signer{ana}, tsa, signedAt, nil), capsule.VerdictSignedIncomplete},
|
||||
"no seal": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, cmstest.Signer{}, signedAt, nil), capsule.VerdictSignedIncomplete},
|
||||
"a seal outside the validity of the certificate": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, certFrom.AddDate(-1, 0, 0), nil), capsule.VerdictSignedIncomplete},
|
||||
"a key 3 beside it": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, signedAt, mustSeal(t, 1, []byte{1})), capsule.VerdictSignedIncomplete},
|
||||
} {
|
||||
if got := capsule.EvaluateSecurityIn(tc.area, c).Signature; got != tc.want {
|
||||
t.Errorf("%s: %s, want %s", name, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
// Another capsule: the signature does not correspond.
|
||||
other := testContext()
|
||||
other.HeadDigest[5] = 9
|
||||
area := cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, signedAt, nil)
|
||||
if got := capsule.EvaluateSecurityIn(area, other).Signature; got != capsule.VerdictSignatureInvalid {
|
||||
t.Errorf("another head: %s", got)
|
||||
}
|
||||
if got := capsule.EvaluateSecurityIn(area, nil).Signature; got != capsule.VerdictSignatureUnchecked {
|
||||
t.Errorf("without a context: %s", got)
|
||||
}
|
||||
|
||||
// F1: SIGNERS out of order or empty, or a CMS that is not one.
|
||||
one, two := sha256Sum([]byte("a")), sha256Sum([]byte("b"))
|
||||
if one[0] > two[0] {
|
||||
one, two = two, one
|
||||
}
|
||||
unsorted := append(append([]byte{0x82, 0x58, 0x20}, two[:]...), append([]byte{0x58, 0x20}, one[:]...)...)
|
||||
for name, signers := range map[string][]byte{"SIGNERS out of order": unsorted, "an empty SIGNERS": {0x80}} {
|
||||
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, signers, []byte{0x30, 0x00})
|
||||
area, _ := capsule.EncodeSecurityWith(content, nil)
|
||||
if got := capsule.EvaluateSecurityIn(area, c).Signature; got != capsule.VerdictSignatureUnchecked {
|
||||
t.Errorf("%s: %s", name, got)
|
||||
}
|
||||
}
|
||||
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, mustSigners(t, ana), []byte("not DER"))
|
||||
area2, _ := capsule.EncodeSecurityWith(content, nil)
|
||||
if got := capsule.EvaluateSecurityIn(area2, c).Signature; got != capsule.VerdictSignatureUnchecked {
|
||||
t.Errorf("not a CMS: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func mustSigners(t *testing.T, s cmstest.Signer) []byte {
|
||||
t.Helper()
|
||||
b, err := capsule.EncodeSigners([][32]byte{sha256Sum(s.Cert.Raw)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func mustSeal(t *testing.T, typ uint64, token []byte) []byte {
|
||||
t.Helper()
|
||||
b, err := capsule.EncodeSeal(typ, token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestEncodeSigners(t *testing.T) {
|
||||
a, b := sha256Sum([]byte("a")), sha256Sum([]byte("b"))
|
||||
if _, err := capsule.EncodeSigners(nil); err == nil {
|
||||
t.Error("an empty list")
|
||||
}
|
||||
if _, err := capsule.EncodeSigners([][32]byte{a, a}); err == nil {
|
||||
t.Error("a certificate twice")
|
||||
}
|
||||
if _, err := capsule.EncodeSigners(make([][32]byte, 17)); err == nil {
|
||||
t.Error("17 certificates")
|
||||
}
|
||||
x, _ := capsule.EncodeSigners([][32]byte{a, b})
|
||||
y, _ := capsule.EncodeSigners([][32]byte{b, a})
|
||||
if string(x) != string(y) || len(x) != 1+2*34 {
|
||||
t.Errorf("not sorted: %x", x)
|
||||
}
|
||||
}
|
||||
|
||||
// Spec v0.11 §29.11: a seal of seal_type 2 seals SEAL_SUBJECT, and gives S4
|
||||
// before the round time, S5 after it, and S3, S2 and S1 for what does not
|
||||
// verify, does not decode or uses another hash.
|
||||
func TestEvaluateSeal(t *testing.T) {
|
||||
tsa := cmstest.NewECDSA("Autoridad de Sellado", elliptic.P256(), certFrom, certTo)
|
||||
c := testContext()
|
||||
key, _ := authorkey.Generate()
|
||||
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgEd25519, nil))
|
||||
sig, _ := capsule.EncodeAuthorSignature(capsule.AlgEd25519, key.Public(), key.Sign(msg))
|
||||
subject := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(sig))
|
||||
area := func(token []byte) []byte {
|
||||
a, err := capsule.EncodeSecurityWith(sig, mustSeal(t, capsule.SealTypeRFC3161, token))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
v := capsule.EvaluateSecurityIn(area(cmstest.Token(subject[:], signedAt, cmstest.TokenOptions{}, tsa)), c)
|
||||
if v.Signature != capsule.VerdictSignedOther || v.Seal != capsule.VerdictSealed || v.Detail == nil || v.Detail.SealHolder != "Autoridad de Sellado" {
|
||||
t.Fatalf("a valid seal: %+v", v)
|
||||
}
|
||||
if lines := v.Lines(); len(lines) != 2 || !strings.Contains(lines[1], "Autoridad de Sellado") || !strings.Contains(lines[1], "2026-09-30T12:00:00Z") {
|
||||
t.Errorf("lines %q", v.Lines())
|
||||
}
|
||||
// Sealed with its own signature part: without key 2 the subject differs.
|
||||
noSig := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(nil))
|
||||
a, _ := capsule.EncodeSecurityWith(nil, mustSeal(t, capsule.SealTypeRFC3161, cmstest.Token(noSig[:], signedAt, cmstest.TokenOptions{}, tsa)))
|
||||
if v := capsule.EvaluateSecurityIn(a, c); v.Signature != capsule.VerdictNoSignature || v.Seal != capsule.VerdictSealed {
|
||||
t.Errorf("a seal without a signature: %+v", v)
|
||||
}
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
token []byte
|
||||
ctx *capsule.SecurityContext
|
||||
want capsule.Verdict
|
||||
}{
|
||||
"after the round time": {cmstest.Token(subject[:], roundTime.Add(time.Minute), cmstest.TokenOptions{}, tsa), c, capsule.VerdictSealedLate},
|
||||
"the accuracy reaches it": {cmstest.Token(subject[:], roundTime.Add(-time.Second), cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa), c, capsule.VerdictSealedLate},
|
||||
"another subject": {cmstest.Token([]byte("other"), signedAt, cmstest.TokenOptions{}, tsa), c, capsule.VerdictSealInvalid},
|
||||
"a TSTInfo of version 2": {cmstest.Token(subject[:], signedAt, cmstest.TokenOptions{Version: 2}, tsa), c, capsule.VerdictSealUnreadable},
|
||||
"not DER": {[]byte("not DER"), c, capsule.VerdictSealUnreadable},
|
||||
"SHA-384 in the imprint": {cmstest.Token(subject[:], signedAt, cmstest.TokenOptions{Hash: crypto.SHA384}, tsa), c, capsule.VerdictSealUnsupported},
|
||||
"the TSA expired at its time": {cmstest.Token(subject[:], certTo.AddDate(1, 0, 0), cmstest.TokenOptions{}, tsa), c, capsule.VerdictSealInvalid},
|
||||
} {
|
||||
if got := capsule.EvaluateSecurityIn(area(tc.token), tc.ctx).Seal; got != tc.want {
|
||||
t.Errorf("%s: %s, want %s", name, got, tc.want)
|
||||
}
|
||||
}
|
||||
// Without a context, as a reader of v0.10: S1.
|
||||
if got := capsule.EvaluateSecurity(area(cmstest.Token(subject[:], signedAt, cmstest.TokenOptions{}, tsa))).Seal; got != capsule.VerdictSealUnsupported {
|
||||
t.Errorf("without a context: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func sha256Sum(b []byte) [32]byte { return sha256.Sum256(b) }
|
||||
Loading…
Reference in new issue