1
15
16 package provenance
17
18 import (
19 "bytes"
20 "crypto"
21 "encoding/hex"
22 "io"
23 "os"
24 "path/filepath"
25 "strings"
26
27 "github.com/pkg/errors"
28 "golang.org/x/crypto/openpgp"
29 "golang.org/x/crypto/openpgp/clearsign"
30 "golang.org/x/crypto/openpgp/packet"
31 "sigs.k8s.io/yaml"
32
33 hapi "helm.sh/helm/v3/pkg/chart"
34 "helm.sh/helm/v3/pkg/chart/loader"
35 )
36
37 var defaultPGPConfig = packet.Config{
38 DefaultHash: crypto.SHA512,
39 }
40
41
42
43
44
45
46
47
48
49
50
51
52
53 type SumCollection struct {
54 Files map[string]string `json:"files"`
55 Images map[string]string `json:"images,omitempty"`
56 }
57
58
59 type Verification struct {
60
61 SignedBy *openpgp.Entity
62
63 FileHash string
64
65 FileName string
66 }
67
68
69
70
71
72
73
74
75 type Signatory struct {
76
77 Entity *openpgp.Entity
78
79 KeyRing openpgp.EntityList
80 }
81
82
83
84
85
86
87
88
89
90
91 func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) {
92 e, err := loadKey(keyfile)
93 if err != nil {
94 return nil, err
95 }
96
97 ring, err := loadKeyRing(keyringfile)
98 if err != nil {
99 return nil, err
100 }
101
102 return &Signatory{
103 Entity: e,
104 KeyRing: ring,
105 }, nil
106 }
107
108
109
110
111
112
113 func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
114 ring, err := loadKeyRing(keyringfile)
115 if err != nil {
116 return nil, err
117 }
118
119 s := &Signatory{KeyRing: ring}
120
121
122 if id == "" {
123 return s, nil
124 }
125
126
127
128
129 var candidate *openpgp.Entity
130 vague := false
131 for _, e := range ring {
132 for n := range e.Identities {
133 if n == id {
134 s.Entity = e
135 return s, nil
136 }
137 if strings.Contains(n, id) {
138 if candidate != nil {
139 vague = true
140 }
141 candidate = e
142 }
143 }
144 }
145 if vague {
146 return s, errors.Errorf("more than one key contain the id %q", id)
147 }
148
149 s.Entity = candidate
150 return s, nil
151 }
152
153
154
155
156
157
158
159 type PassphraseFetcher func(name string) ([]byte, error)
160
161
162
163
164
165
166
167
168
169
170
171 func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
172 if s.Entity == nil {
173 return errors.New("private key not found")
174 } else if s.Entity.PrivateKey == nil {
175 return errors.New("provided key is not a private key. Try providing a keyring with secret keys")
176 }
177
178
179 if !s.Entity.PrivateKey.Encrypted {
180 return nil
181 }
182
183 fname := "Unknown"
184 for i := range s.Entity.Identities {
185 if i != "" {
186 fname = i
187 break
188 }
189 }
190
191 p, err := fn(fname)
192 if err != nil {
193 return err
194 }
195
196 return s.Entity.PrivateKey.Decrypt(p)
197 }
198
199
200
201
202
203
204
205 func (s *Signatory) ClearSign(chartpath string) (string, error) {
206 if s.Entity == nil {
207 return "", errors.New("private key not found")
208 } else if s.Entity.PrivateKey == nil {
209 return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys")
210 }
211
212 if fi, err := os.Stat(chartpath); err != nil {
213 return "", err
214 } else if fi.IsDir() {
215 return "", errors.New("cannot sign a directory")
216 }
217
218 out := bytes.NewBuffer(nil)
219
220 b, err := messageBlock(chartpath)
221 if err != nil {
222 return "", err
223 }
224
225
226 w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig)
227 if err != nil {
228 return "", err
229 }
230
231 _, err = io.Copy(w, b)
232
233 if err != nil {
234
235
236
237
238
239 return "", errors.Wrap(err, "failed to write to clearsign encoder")
240 }
241
242 err = w.Close()
243 if err != nil {
244 return "", errors.Wrap(err, "failed to either sign or armor message block")
245 }
246
247 return out.String(), nil
248 }
249
250
251 func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
252 ver := &Verification{}
253 for _, fname := range []string{chartpath, sigpath} {
254 if fi, err := os.Stat(fname); err != nil {
255 return ver, err
256 } else if fi.IsDir() {
257 return ver, errors.Errorf("%s cannot be a directory", fname)
258 }
259 }
260
261
262 sig, err := s.decodeSignature(sigpath)
263 if err != nil {
264 return ver, errors.Wrap(err, "failed to decode signature")
265 }
266
267 by, err := s.verifySignature(sig)
268 if err != nil {
269 return ver, err
270 }
271 ver.SignedBy = by
272
273
274 sum, err := DigestFile(chartpath)
275 if err != nil {
276 return ver, err
277 }
278 _, sums, err := parseMessageBlock(sig.Plaintext)
279 if err != nil {
280 return ver, err
281 }
282
283 sum = "sha256:" + sum
284 basename := filepath.Base(chartpath)
285 if sha, ok := sums.Files[basename]; !ok {
286 return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename)
287 } else if sha != sum {
288 return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
289 }
290 ver.FileHash = sum
291 ver.FileName = basename
292
293
294
295 return ver, nil
296 }
297
298 func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
299 data, err := os.ReadFile(filename)
300 if err != nil {
301 return nil, err
302 }
303
304 block, _ := clearsign.Decode(data)
305 if block == nil {
306
307 return nil, errors.New("signature block not found")
308 }
309
310 return block, nil
311 }
312
313
314 func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) {
315 return openpgp.CheckDetachedSignature(
316 s.KeyRing,
317 bytes.NewBuffer(block.Bytes),
318 block.ArmoredSignature.Body,
319 )
320 }
321
322 func messageBlock(chartpath string) (*bytes.Buffer, error) {
323 var b *bytes.Buffer
324
325 chash, err := DigestFile(chartpath)
326 if err != nil {
327 return b, err
328 }
329
330 base := filepath.Base(chartpath)
331 sums := &SumCollection{
332 Files: map[string]string{
333 base: "sha256:" + chash,
334 },
335 }
336
337
338 chart, err := loader.LoadFile(chartpath)
339 if err != nil {
340 return b, err
341 }
342
343
344 data, err := yaml.Marshal(chart.Metadata)
345 if err != nil {
346 return b, err
347 }
348
349
350
351
352 b = bytes.NewBuffer(data)
353 b.WriteString("\n...\n")
354
355 data, err = yaml.Marshal(sums)
356 if err != nil {
357 return b, err
358 }
359 b.Write(data)
360
361 return b, nil
362 }
363
364
365 func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) {
366
367 parts := bytes.Split(data, []byte("\n...\n"))
368 if len(parts) < 2 {
369 return nil, nil, errors.New("message block must have at least two parts")
370 }
371
372 md := &hapi.Metadata{}
373 sc := &SumCollection{}
374
375 if err := yaml.Unmarshal(parts[0], md); err != nil {
376 return md, sc, err
377 }
378 err := yaml.Unmarshal(parts[1], sc)
379 return md, sc, err
380 }
381
382
383 func loadKey(keypath string) (*openpgp.Entity, error) {
384 f, err := os.Open(keypath)
385 if err != nil {
386 return nil, err
387 }
388 defer f.Close()
389
390 pr := packet.NewReader(f)
391 return openpgp.ReadEntity(pr)
392 }
393
394 func loadKeyRing(ringpath string) (openpgp.EntityList, error) {
395 f, err := os.Open(ringpath)
396 if err != nil {
397 return nil, err
398 }
399 defer f.Close()
400 return openpgp.ReadKeyRing(f)
401 }
402
403
404
405
406
407
408
409 func DigestFile(filename string) (string, error) {
410 f, err := os.Open(filename)
411 if err != nil {
412 return "", err
413 }
414 defer f.Close()
415 return Digest(f)
416 }
417
418
419
420
421 func Digest(in io.Reader) (string, error) {
422 hash := crypto.SHA256.New()
423 if _, err := io.Copy(hash, in); err != nil {
424 return "", nil
425 }
426 return hex.EncodeToString(hash.Sum(nil)), nil
427 }
428
View as plain text