|
| 1 | +package torchwood |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | + "strconv" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "golang.org/x/mod/sumdb/note" |
| 10 | +) |
| 11 | + |
| 12 | +// Policy encodes the requirements for a set of (co)signatures on a [note.Note]. |
| 13 | +// |
| 14 | +// The Verifier method allows the policy to be passed in as the known parameter |
| 15 | +// to [note.Open], while the Check method must be applied to [note.Note.Sigs] |
| 16 | +// after [note.Open]. |
| 17 | +type Policy interface { |
| 18 | + // Check returns nil if the provided signatures satisfy the policy. |
| 19 | + // |
| 20 | + // The signatures must already have been verified with their respective |
| 21 | + // verifiers, and would usually be obtained from [note.Note.Sigs]. |
| 22 | + Check(sigs []note.Signature) error |
| 23 | + |
| 24 | + // Verifier implements [note.Verifiers], returning the Verifier for any of |
| 25 | + // the cosigners in the policy. |
| 26 | + Verifier(name string, hash uint32) (note.Verifier, error) |
| 27 | +} |
| 28 | + |
| 29 | +// SingleVerifierPolicy returns a Policy that requires a single verifier to have |
| 30 | +// signed the note. |
| 31 | +func SingleVerifierPolicy(v note.Verifier) Policy { |
| 32 | + return &singleVerifierPolicy{v: v} |
| 33 | +} |
| 34 | + |
| 35 | +type singleVerifierPolicy struct { |
| 36 | + v note.Verifier |
| 37 | +} |
| 38 | + |
| 39 | +func (w *singleVerifierPolicy) Check(sigs []note.Signature) error { |
| 40 | + for _, sig := range sigs { |
| 41 | + if sig.Name == w.v.Name() && sig.Hash == w.v.KeyHash() { |
| 42 | + return nil |
| 43 | + } |
| 44 | + } |
| 45 | + return fmt.Errorf("verifier %q (%08x) did not sign", w.v.Name(), w.v.KeyHash()) |
| 46 | +} |
| 47 | + |
| 48 | +func (w *singleVerifierPolicy) Verifier(name string, hash uint32) (note.Verifier, error) { |
| 49 | + if name == w.v.Name() && hash == w.v.KeyHash() { |
| 50 | + return w.v, nil |
| 51 | + } |
| 52 | + return nil, ¬e.UnknownVerifierError{Name: name, KeyHash: hash} |
| 53 | +} |
| 54 | + |
| 55 | +// ThresholdPolicy returns a Policy that requires at least n of the |
| 56 | +// provided policies to be satisfied. |
| 57 | +// |
| 58 | +// It panics if n is less than zero or greater than the number of polcies. |
| 59 | +func ThresholdPolicy(n int, policies ...Policy) Policy { |
| 60 | + if n < 0 || n > len(policies) { |
| 61 | + panic(fmt.Errorf("threshold of %d outside bounds for policies %s", n, policies)) |
| 62 | + } |
| 63 | + return &thresholdPolicy{ |
| 64 | + policies: policies, |
| 65 | + threshold: n, |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +type thresholdPolicy struct { |
| 70 | + policies []Policy |
| 71 | + threshold int |
| 72 | +} |
| 73 | + |
| 74 | +func (w *thresholdPolicy) Check(sigs []note.Signature) error { |
| 75 | + satisfied := 0 |
| 76 | + for _, p := range w.policies { |
| 77 | + if err := p.Check(sigs); err == nil { |
| 78 | + satisfied++ |
| 79 | + } |
| 80 | + } |
| 81 | + if satisfied >= w.threshold { |
| 82 | + return nil |
| 83 | + } |
| 84 | + return fmt.Errorf("only %d/%d required policies satisfied", satisfied, w.threshold) |
| 85 | +} |
| 86 | + |
| 87 | +func (w *thresholdPolicy) Verifier(name string, hash uint32) (note.Verifier, error) { |
| 88 | + var verifier note.Verifier |
| 89 | + for _, p := range w.policies { |
| 90 | + v, err := p.Verifier(name, hash) |
| 91 | + if _, ok := err.(*note.UnknownVerifierError); ok { |
| 92 | + continue |
| 93 | + } |
| 94 | + if err != nil { |
| 95 | + return nil, err |
| 96 | + } |
| 97 | + if verifier != nil { |
| 98 | + // This, for now, requires not having the same verifier in multiple |
| 99 | + // groups, which matches the Sigsum policy specification. If we |
| 100 | + // change our mind, we will need some way to check the verifiers for |
| 101 | + // equality. |
| 102 | + return nil, fmt.Errorf("multiple verifiers found for %q (%08x)", name, hash) |
| 103 | + } |
| 104 | + verifier = v |
| 105 | + } |
| 106 | + if verifier != nil { |
| 107 | + return verifier, nil |
| 108 | + } |
| 109 | + return nil, ¬e.UnknownVerifierError{Name: name, KeyHash: hash} |
| 110 | +} |
| 111 | + |
| 112 | +// ParsePolicy parses a witness policy from the provided byte slice. |
| 113 | +// |
| 114 | +// As the policy format doesn't currently support specifying a log vkey, the |
| 115 | +// return value would usually be paired with a [note.Verifier] with |
| 116 | +// [ThresholdPolicy] and a threshold of 2-of-2. The log origin also needs to be |
| 117 | +// passed to [VerifyProof] or [VerifyCheckpoint]. |
| 118 | +// |
| 119 | +// The policy format is EXPERIMENTAL and may change in future releases. It is |
| 120 | +// based on [the Sigsum policy format] but it uses vkeys instead of raw public |
| 121 | +// keys. It is compatible with Tessera witness policies. |
| 122 | +// |
| 123 | +// [the Sigsum policy format]: https://git.glasklar.is/sigsum/core/sigsum-go/-/blob/main/doc/policy.md |
| 124 | +func ParsePolicy(p []byte) (Policy, error) { |
| 125 | + var quorum string |
| 126 | + policies := make(map[string]Policy) |
| 127 | + for i, line := range strings.Split(string(p), "\n") { |
| 128 | + line, _, _ = strings.Cut(line, "#") |
| 129 | + if strings.Trim(line, " \t") == "" { |
| 130 | + continue |
| 131 | + } |
| 132 | + switch fields := strings.Fields(line); fields[0] { |
| 133 | + case "witness": |
| 134 | + if len(fields) < 3 { |
| 135 | + return nil, fmt.Errorf("line %d: invalid witness definition: %q", i+1, line) |
| 136 | + } |
| 137 | + name, vkey := fields[1], fields[2] |
| 138 | + if _, ok := policies[name]; ok { |
| 139 | + return nil, fmt.Errorf("line %d: duplicate component name: %q", i+1, name) |
| 140 | + } |
| 141 | + v, err := NewCosignatureVerifier(vkey) |
| 142 | + if err != nil { |
| 143 | + return nil, fmt.Errorf("line %d: invalid witness vkey %q: %w", i+1, vkey, err) |
| 144 | + } |
| 145 | + policies[name] = SingleVerifierPolicy(v) |
| 146 | + case "group": |
| 147 | + if len(fields) < 4 { |
| 148 | + return nil, fmt.Errorf("line %d: invalid group definition: %q", i+1, line) |
| 149 | + } |
| 150 | + name, nStr, children := fields[1], fields[2], fields[3:] |
| 151 | + if _, ok := policies[name]; ok { |
| 152 | + return nil, fmt.Errorf("line %d: duplicate component name: %q", i+1, name) |
| 153 | + } |
| 154 | + var n int |
| 155 | + switch nStr { |
| 156 | + case "any": |
| 157 | + n = 1 |
| 158 | + case "all": |
| 159 | + n = len(children) |
| 160 | + default: |
| 161 | + var err error |
| 162 | + n, err = strconv.Atoi(nStr) |
| 163 | + if err != nil || n < 1 || n > len(children) { |
| 164 | + return nil, fmt.Errorf("line %d: invalid group threshold %q", i+1, nStr) |
| 165 | + } |
| 166 | + } |
| 167 | + c := make([]Policy, 0, len(children)) |
| 168 | + for _, cn := range children { |
| 169 | + child, ok := policies[cn] |
| 170 | + if !ok { |
| 171 | + return nil, fmt.Errorf("line %d: unknown component %q in group %q definition", i+1, cn, name) |
| 172 | + } |
| 173 | + c = append(c, child) |
| 174 | + } |
| 175 | + policies[name] = ThresholdPolicy(n, c...) |
| 176 | + case "quorum": |
| 177 | + if len(fields) != 2 { |
| 178 | + return nil, fmt.Errorf("line %d: invalid quorum definition: %q", i+1, line) |
| 179 | + } |
| 180 | + if quorum != "" { |
| 181 | + return nil, fmt.Errorf("line %d: multiple quorum definitions", i+1) |
| 182 | + } |
| 183 | + quorum = fields[1] |
| 184 | + default: |
| 185 | + return nil, fmt.Errorf("line %d: unknown keyword: %q", i+1, fields[0]) |
| 186 | + } |
| 187 | + } |
| 188 | + switch quorum { |
| 189 | + case "": |
| 190 | + return nil, errors.New("no quorum defined in policy") |
| 191 | + case "none": |
| 192 | + return ThresholdPolicy(0), nil |
| 193 | + default: |
| 194 | + policy, ok := policies[quorum] |
| 195 | + if !ok { |
| 196 | + return nil, fmt.Errorf("quorum %q not defined in policy", quorum) |
| 197 | + } |
| 198 | + return policy, nil |
| 199 | + } |
| 200 | +} |
0 commit comments