From ce83a8a7776809d2257cc966f62d2cc857cf672b Mon Sep 17 00:00:00 2001 From: Matt Butcher <mbutcher@engineyard.com> Date: Mon, 25 Jul 2016 17:34:05 -0600 Subject: [PATCH] feat(pkg/provenance): add OpenPGP signatures This adds support for OpenPGP signatures containing provenance data. Such information can be used to verify the integrity of a Chart by testing that its file hash, metadata, and images are correct. This first PR does not contain all of the tooling necessary for end-to-end chart integrity. It contains just the library. See #983 --- glide.lock | 15 +- glide.yaml | 3 + pkg/provenance/doc.go | 37 +++ pkg/provenance/sign.go | 280 ++++++++++++++++++ pkg/provenance/sign_test.go | 249 ++++++++++++++++ pkg/provenance/testdata/hashtest-1.2.3.tgz | Bin 0 -> 465 bytes pkg/provenance/testdata/hashtest.sha256 | 1 + pkg/provenance/testdata/hashtest/.helmignore | 5 + pkg/provenance/testdata/hashtest/Chart.yaml | 3 + pkg/provenance/testdata/hashtest/values.yaml | 4 + pkg/provenance/testdata/helm-test-key.pub | Bin 0 -> 1243 bytes pkg/provenance/testdata/helm-test-key.secret | Bin 0 -> 2545 bytes pkg/provenance/testdata/msgblock.yaml | 7 + pkg/provenance/testdata/msgblock.yaml.asc | 21 ++ .../testdata/msgblock.yaml.tampered | 21 ++ pkg/provenance/testdata/regen-hashtest.sh | 3 + 16 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 pkg/provenance/doc.go create mode 100644 pkg/provenance/sign.go create mode 100644 pkg/provenance/sign_test.go create mode 100644 pkg/provenance/testdata/hashtest-1.2.3.tgz create mode 100644 pkg/provenance/testdata/hashtest.sha256 create mode 100644 pkg/provenance/testdata/hashtest/.helmignore create mode 100755 pkg/provenance/testdata/hashtest/Chart.yaml create mode 100644 pkg/provenance/testdata/hashtest/values.yaml create mode 100644 pkg/provenance/testdata/helm-test-key.pub create mode 100644 pkg/provenance/testdata/helm-test-key.secret create mode 100644 pkg/provenance/testdata/msgblock.yaml create mode 100644 pkg/provenance/testdata/msgblock.yaml.asc create mode 100644 pkg/provenance/testdata/msgblock.yaml.tampered create mode 100755 pkg/provenance/testdata/regen-hashtest.sh diff --git a/glide.lock b/glide.lock index 1bf1d6940..808be278c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 410e784360a10f716d4bf4d22decf81f75b327d051b3f2d23f55aa9049c09676 -updated: 2016-08-19T12:19:48.074620307-06:00 +hash: 05c56f2ae4c8bcbaf2c428e2e070ec00f865b284ea61dd671e2c4e117f2d6528 +updated: 2016-08-19T17:30:32.462379907-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 @@ -247,6 +247,17 @@ imports: subpackages: - codec - codec/codecgen +- name: golang.org/x/crypto + version: c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3 + subpackages: + - cast5 + - openpgp + - openpgp/armor + - openpgp/clearsign + - openpgp/elgamal + - openpgp/errors + - openpgp/packet + - openpgp/s2k - name: golang.org/x/net version: fb93926129b8ec0056f2f458b1f519654814edf0 subpackages: diff --git a/glide.yaml b/glide.yaml index ed96b0204..2f7567469 100644 --- a/glide.yaml +++ b/glide.yaml @@ -50,3 +50,6 @@ import: - package: google.golang.org/cloud vcs: git repo: https://code.googlesource.com/gocloud +- package: golang.org/x/crypto + subpackages: + - openpgp diff --git a/pkg/provenance/doc.go b/pkg/provenance/doc.go new file mode 100644 index 000000000..dacfa9e69 --- /dev/null +++ b/pkg/provenance/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/*Package provenance provides tools for establishing the authenticity of a chart. + +In Helm, provenance is established via several factors. The primary factor is the +cryptographic signature of a chart. Chart authors may sign charts, which in turn +provide the necessary metadata to ensure the integrity of the chart file, the +Chart.yaml, and the referenced Docker images. + +A provenance file is clear-signed. This provides cryptographic verification that +a particular block of information (Chart.yaml, archive file, images) have not +been tampered with or altered. To learn more, read the GnuPG documentation on +clear signatures: +https://www.gnupg.org/gph/en/manual/x135.html + +The cryptography used by Helm should be compatible with OpenGPG. For example, +you should be able to verify a signature by importing the desired public key +and using `gpg --verify`, `keybase pgp verify`, or similar: + + $ gpg --verify some.sig + gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 + gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>" [ultimate] +*/ +package provenance // import "k8s.io/helm/pkg/provenance" diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go new file mode 100644 index 000000000..3dd8cfb74 --- /dev/null +++ b/pkg/provenance/sign.go @@ -0,0 +1,280 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provenance + +import ( + "bytes" + "crypto" + "encoding/hex" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/clearsign" + "golang.org/x/crypto/openpgp/packet" + + "k8s.io/helm/pkg/chartutil" + hapi "k8s.io/helm/pkg/proto/hapi/chart" +) + +var defaultPGPConfig = packet.Config{ + DefaultHash: crypto.SHA512, +} + +// SumCollection represents a collecton of file and image checksums. +// +// Files are of the form: +// FILENAME: "sha256:SUM" +// Images are of the form: +// "IMAGE:TAG": "sha256:SUM" +// Docker optionally supports sha512, and if this is the case, the hash marker +// will be 'sha512' instead of 'sha256'. +type SumCollection struct { + Files map[string]string `json:"files"` + Images map[string]string `json:"images,omitempty"` +} + +// Signatory signs things. +// +// Signatories can be constructed from a PGP private key file using NewFromFiles +// or they can be constructed manually by setting the Entity to a valid +// PGP entity. +// +// The same Signatory can be used to sign or validate multiple charts. +type Signatory struct { + // The signatory for this instance of Helm. This is used for signing. + Entity *openpgp.Entity + // The keyring for this instance of Helm. This is used for verification. + KeyRing openpgp.EntityList +} + +// NewFromFiles constructs a new Signatory from the PGP key in the given filename. +// +// This will emit an error if it cannot find a valid GPG keyfile (entity) at the +// given location. +// +// Note that the keyfile may have just a public key, just a private key, or +// both. The Signatory methods may have different requirements of the keys. For +// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it +// can sign something. +func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) { + e, err := loadKey(keyfile) + if err != nil { + return nil, err + } + + ring, err := loadKeyRing(keyringfile) + if err != nil { + return nil, err + } + + return &Signatory{ + Entity: e, + KeyRing: ring, + }, nil +} + +// Sign signs a chart with the given key. +// +// This takes the path to a chart archive file and a key, and it returns a clear signature. +// +// The Signatory must have a valid Entity.PrivateKey for this to work. If it does +// not, an error will be returned. +func (s *Signatory) ClearSign(chartpath string) (string, error) { + if s.Entity.PrivateKey == nil { + return "", errors.New("private key not found") + } + + if fi, err := os.Stat(chartpath); err != nil { + return "", err + } else if fi.IsDir() { + return "", errors.New("cannot sign a directory") + } + + out := bytes.NewBuffer(nil) + + b, err := messageBlock(chartpath) + if err != nil { + return "", nil + } + + // Sign the buffer + w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig) + if err != nil { + return "", err + } + _, err = io.Copy(w, b) + w.Close() + return out.String(), err +} + +func (s *Signatory) Verify(chartpath, sigpath string) (bool, error) { + for _, fname := range []string{chartpath, sigpath} { + if fi, err := os.Stat(fname); err != nil { + return false, err + } else if fi.IsDir() { + return false, fmt.Errorf("%s cannot be a directory", fname) + } + } + + // First verify the signature + sig, err := s.decodeSignature(sigpath) + if err != nil { + return false, fmt.Errorf("failed to decode signature: %s", err) + } + + by, err := s.verifySignature(sig) + if err != nil { + return false, err + } + for n := range by.Identities { + log.Printf("info: %s signed by %q", sigpath, n) + } + + // Second, verify the hash of the tarball. + + return true, nil +} + +func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + block, _ := clearsign.Decode(data) + if block == nil { + // There was no sig in the file. + return nil, errors.New("signature block not found") + } + + return block, nil +} + +// verifySignature verifies that the given block is validly signed, and returns the signer. +func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) { + return openpgp.CheckDetachedSignature( + s.KeyRing, + bytes.NewBuffer(block.Bytes), + block.ArmoredSignature.Body, + ) +} + +func messageBlock(chartpath string) (*bytes.Buffer, error) { + var b *bytes.Buffer + // Checksum the archive + chash, err := sumArchive(chartpath) + if err != nil { + return b, err + } + + base := filepath.Base(chartpath) + sums := &SumCollection{ + Files: map[string]string{ + base: "sha256:" + chash, + }, + } + + // Load the archive into memory. + chart, err := chartutil.LoadFile(chartpath) + if err != nil { + return b, err + } + + // Buffer a hash + checksums YAML file + data, err := yaml.Marshal(chart.Metadata) + if err != nil { + return b, err + } + + // FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP + // clearsign block. So we use ...\n, which is the YAML document end marker. + // http://yaml.org/spec/1.2/spec.html#id2800168 + b = bytes.NewBuffer(data) + b.WriteString("\n...\n") + + data, err = yaml.Marshal(sums) + if err != nil { + return b, err + } + b.Write(data) + + return b, nil +} + +// parseMessageBlock +func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) { + // This sucks. + parts := bytes.Split(data, []byte("\n...\n")) + if len(parts) < 2 { + return nil, nil, errors.New("message block must have at least two parts") + } + + md := &hapi.Metadata{} + sc := &SumCollection{} + + if err := yaml.Unmarshal(parts[0], md); err != nil { + return md, sc, err + } + err := yaml.Unmarshal(parts[1], sc) + return md, sc, err +} + +// loadKey loads a GPG key found at a particular path. +func loadKey(keypath string) (*openpgp.Entity, error) { + f, err := os.Open(keypath) + if err != nil { + return nil, err + } + defer f.Close() + + pr := packet.NewReader(f) + return openpgp.ReadEntity(pr) +} + +func loadKeyRing(ringpath string) (openpgp.EntityList, error) { + f, err := os.Open(ringpath) + if err != nil { + return nil, err + } + defer f.Close() + return openpgp.ReadKeyRing(f) +} + +// sumArchive calculates a SHA256 hash (like Docker) for a given file. +// +// It takes the path to the archive file, and returns a string representation of +// the SHA256 sum. +// +// The intended use of this function is to generate a sum of a chart TGZ file. +func sumArchive(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + + hash := crypto.SHA256.New() + io.Copy(hash, f) + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go new file mode 100644 index 000000000..5dfd6bd68 --- /dev/null +++ b/pkg/provenance/sign_test.go @@ -0,0 +1,249 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provenance + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + pgperrors "golang.org/x/crypto/openpgp/errors" +) + +const ( + // testKeyFile is the secret key. + // Generating keys should be done with `gpg --gen-key`. The current key + // was generated to match Go's defaults (RSA/RSA 2048). It has no pass + // phrase. Use `gpg --export-secret-keys helm-test` to export the secret. + testKeyfile = "testdata/helm-test-key.secret" + + // testPubfile is the public key file. + // Use `gpg --export helm-test` to export the public key. + testPubfile = "testdata/helm-test-key.pub" + + // Generated name for the PGP key in testKeyFile. + testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>` + + testChartfile = "testdata/hashtest-1.2.3.tgz" + + // testSigBlock points to a signature generated by an external tool. + // This file was generated with GnuPG: + // gpg --clearsign -u helm-test --openpgp testdata/msgblock.yaml + testSigBlock = "testdata/msgblock.yaml.asc" + + // testTamperedSigBlock is a tampered copy of msgblock.yaml.asc + testTamperedSigBlock = "testdata/msgblock.yaml.tampered" + + // testSumfile points to a SHA256 sum generated by an external tool. + // We always want to validate against an external tool's representation to + // verify that we haven't done something stupid. This file was generated + // with shasum. + // shasum -a 256 hashtest-1.2.3.tgz > testdata/hashtest.sha256 + testSumfile = "testdata/hashtest.sha256" +) + +// testMessageBlock represents the expected message block for the testdata/hashtest chart. +const testMessageBlock = `description: Test chart versioning +name: hashtest +version: 1.2.3 + +... +files: + hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 +` + +func TestMessageBlock(t *testing.T) { + out, err := messageBlock(testChartfile) + if err != nil { + t.Fatal(err) + } + got := out.String() + + if got != testMessageBlock { + t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got) + } +} + +func TestParseMessageBlock(t *testing.T) { + md, sc, err := parseMessageBlock([]byte(testMessageBlock)) + if err != nil { + t.Fatal(err) + } + + if md.Name != "hashtest" { + t.Errorf("Expected name %q, got %q", "hashtest", md.Name) + } + + if lsc := len(sc.Files); lsc != 1 { + t.Errorf("Expected 1 file, got %d", lsc) + } + + if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok { + t.Errorf("hashtest file not found in Files") + } else if hash != "sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75" { + t.Errorf("Unexpected hash: %q", hash) + } +} + +func TestLoadKey(t *testing.T) { + k, err := loadKey(testKeyfile) + if err != nil { + t.Fatal(err) + } + + if _, ok := k.Identities[testKeyName]; !ok { + t.Errorf("Expected to load a key for user %q", testKeyName) + } +} + +func TestLoadKeyRing(t *testing.T) { + k, err := loadKeyRing(testPubfile) + if err != nil { + t.Fatal(err) + } + + if len(k) > 1 { + t.Errorf("Expected 1, got %d", len(k)) + } + + for _, e := range k { + if ii, ok := e.Identities[testKeyName]; !ok { + t.Errorf("Expected %s in %v", testKeyName, ii) + } + } +} + +func TestNewFromFiles(t *testing.T) { + s, err := NewFromFiles(testKeyfile, testPubfile) + if err != nil { + t.Fatal(err) + } + + if _, ok := s.Entity.Identities[testKeyName]; !ok { + t.Errorf("Expected to load a key for user %q", testKeyName) + } +} + +func TestSumArchive(t *testing.T) { + hash, err := sumArchive(testChartfile) + if err != nil { + t.Fatal(err) + } + + sig, err := readSumFile(testSumfile) + if err != nil { + t.Fatal(err) + } + + if !strings.Contains(sig, hash) { + t.Errorf("Expected %s to be in %s", hash, sig) + } +} + +func TestClearSign(t *testing.T) { + signer, err := NewFromFiles(testKeyfile, testPubfile) + if err != nil { + t.Fatal(err) + } + + sig, err := signer.ClearSign(testChartfile) + if err != nil { + t.Fatal(err) + } + t.Logf("Sig:\n%s", sig) + + if !strings.Contains(sig, testMessageBlock) { + t.Errorf("expected message block to be in sig: %s", sig) + } +} + +func TestDecodeSignature(t *testing.T) { + // Unlike other tests, this does a round-trip test, ensuring that a signature + // generated by the library can also be verified by the library. + + signer, err := NewFromFiles(testKeyfile, testPubfile) + if err != nil { + t.Fatal(err) + } + + sig, err := signer.ClearSign(testChartfile) + if err != nil { + t.Fatal(err) + } + + f, err := ioutil.TempFile("", "helm-test-sig-") + if err != nil { + t.Fatal(err) + } + + tname := f.Name() + defer func() { + os.Remove(tname) + }() + f.WriteString(sig) + f.Close() + + sig2, err := signer.decodeSignature(tname) + if err != nil { + t.Fatal(err) + } + + by, err := signer.verifySignature(sig2) + if err != nil { + t.Fatal(err) + } + + if _, ok := by.Identities[testKeyName]; !ok { + t.Errorf("Expected identity %q", testKeyName) + } +} + +func TestVerify(t *testing.T) { + signer, err := NewFromFiles(testKeyfile, testPubfile) + if err != nil { + t.Fatal(err) + } + + passed, err := signer.Verify(testChartfile, testSigBlock) + if !passed { + t.Errorf("Failed to pass verify. Err: %s", err) + } + + passed, err = signer.Verify(testChartfile, testTamperedSigBlock) + if passed { + t.Errorf("Expected %s to fail.", testTamperedSigBlock) + } + + switch err.(type) { + case pgperrors.SignatureError: + t.Logf("Tampered sig block error: %s (%T)", err, err) + default: + t.Errorf("Expected invalid signature error, got %q (%T)", err, err) + } +} + +// readSumFile reads a file containing a sum generated by the UNIX shasum tool. +func readSumFile(sumfile string) (string, error) { + data, err := ioutil.ReadFile(sumfile) + if err != nil { + return "", err + } + + sig := string(data) + parts := strings.SplitN(sig, " ", 2) + return parts[0], nil +} diff --git a/pkg/provenance/testdata/hashtest-1.2.3.tgz b/pkg/provenance/testdata/hashtest-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1e89b524f4fc89e4a483ef8dc4fefdc79d64786f GIT binary patch literal 465 zcmV;?0WSU@iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL2*>(ek4$A9Pk6;F0i(Ac#6HrP$vQBl|~o+N8un_!xhB;DM9 z?@Oyirm(~2hL8_~X6Z|tviJF}Qg|8Ahqv#gaDkmfr=M<3POP4v$0Kom%z4h|@i@<m z<tl%In{VWA3_b`)x4-r}=MVRvP@}J$>VvXo4LfQCsA40)0iCBgW!lV$4%hIjQL>+B z*1%c8%Iwh(khqH3|AWv2`hOAtm;5hydFFq~%Od%I4;HY&Mhu#a9~%G~>t@$kwt$^f z9_S<B%e#*s-q7hzgn%_PN@Fb8V5Nmah%qsB5%fYi55dAdj=iU9W$5e44h{l#y%u!& zxOmEzaaBr^M=<-(B<fDJ#yY(9JudpcT>jxd=gI$juz=4|XJ<xVr{5or5)JGJrEB{C zCxfeRqMdvl)4&gXZ(X1P^^AsA+dWVyRFbM+gF~`g9isU`{x&a69fikM#Fq_t**k31 zcZ!CiqPiEa7<@AN1lEiz<CdUD2U;158MfJe-3HoQ##$9pR3s9K#MS%=00960=<3*) H01yBG#+u^@ literal 0 HcmV?d00001 diff --git a/pkg/provenance/testdata/hashtest.sha256 b/pkg/provenance/testdata/hashtest.sha256 new file mode 100644 index 000000000..829031f9d --- /dev/null +++ b/pkg/provenance/testdata/hashtest.sha256 @@ -0,0 +1 @@ +8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 hashtest-1.2.3.tgz diff --git a/pkg/provenance/testdata/hashtest/.helmignore b/pkg/provenance/testdata/hashtest/.helmignore new file mode 100644 index 000000000..435b756d8 --- /dev/null +++ b/pkg/provenance/testdata/hashtest/.helmignore @@ -0,0 +1,5 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +.git diff --git a/pkg/provenance/testdata/hashtest/Chart.yaml b/pkg/provenance/testdata/hashtest/Chart.yaml new file mode 100755 index 000000000..342631ef8 --- /dev/null +++ b/pkg/provenance/testdata/hashtest/Chart.yaml @@ -0,0 +1,3 @@ +description: Test chart versioning +name: hashtest +version: 1.2.3 diff --git a/pkg/provenance/testdata/hashtest/values.yaml b/pkg/provenance/testdata/hashtest/values.yaml new file mode 100644 index 000000000..0827a01fb --- /dev/null +++ b/pkg/provenance/testdata/hashtest/values.yaml @@ -0,0 +1,4 @@ +# Default values for hashtest. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/pkg/provenance/testdata/helm-test-key.pub b/pkg/provenance/testdata/helm-test-key.pub new file mode 100644 index 0000000000000000000000000000000000000000..38714f25adaf701b08e11fd559a587074bbde0e4 GIT binary patch literal 1243 zcmV<11SI>J0SyFKmTjH^2mr{k15wFPQdpTAAE<XxWtXrITzgx-V5`~*fl`c%qbny9 z0-%CFx|COAqyK?6%|{LoU;O@OgTmzJH8S&cEI7J5MU<pc!@S;=0XnlA$PYq{(v%A9 z3`0$OCjYP>clY4oV^-7nTy5x$&xF;PC4il}o-Pk4@#$knlp#(|J0GE?qli_lr;7-o zyY8vBsN_GJe;#w<`JdR7riNL&RJlcS)FG+W=91;dYS6NZ2tY?kZ8Sw9{r=e4|L3E{ zRod|EPC!PWgW&pe&4qiqKQAijj;G~fyjcC^m%0p54Tn{h%YFKd0VC4n=#~SRc@BVd znj66*)Om%*SEQfX{1*Tb0RRECT}WkYZ6H)-b98BLXCNq4XlZjGYh`&Lb7*gMY-AvB zZftoVVr3w8b7f>8W^ZyJbY*jNX>MmOAVg0fPES-IR8mz_R4yqXJZNQXZ7p<Vb98BL zXFzCWY;7)cXg-MnHv|&_3IHJm0#}x8o&p;K1q%rX2LlBa3JC}c1r`DW0RRFX0Rk6* z0162Zggd{OAHj!WiA4wiwE5`M<<b|@JCT==9UG;<lt=v{x1o_)XwuGG8&w>6uVakV zT7GGV$jaKjyjfI_a~N1!Hk?5C$0wa&4)R=i$v7t&ZMycW#RkavpF%A?><BFeV@yae zEr<1?&8i4mWc$i527Fm5toJ9B3aL#!%(J%00`4P`QwSw4q@B(fipspQcpqr&Mo;;U z3W$QDXti(n1+3cRH>MTT2anNDzOQUm<++zEOykJ9-@&c2QXq3owqf7fek`=L@+7iF zv;IW2Q>Q&r+V@cWDF&hAUUsCKlDinerKgvJUJCl$5gjb7NhM{mBP%!M^mX-iS8xFf zuLB{@MDqvtZzF#Bxd9CXSC(y_0SExW>8~h=U8|!do4*OJj2u#!KD<F};x(4i3gm1v zg^boLy7o&cBy?~jMMx8xWT98p>e3v+1T<DcW~-y${+x<<z!#@Wyl$RK(q%+qDmDG5 zVl2(%UtFbnh08TY^?=*#lcK5U7D7<eW{jy@D;B9Fq2@2@DPP4Xc(;TpC<!#bBfgF2 zMhHH@gXdH_BF{eZ({Xf@gqwiTouCiJwVOY^#H$hf&MpErM-&ovKi)n}VD&rD$^{Z) z02OVCKuZpnGG~9yIOKdkYHLY%Stp}TD<|`o`qx#f$T~oYi5LrUvn$9uOVx0Y2JNrE zTx@T(b~!-dp}<O*rTa8>+aVzU5di=Ji2)x37y$|Z2?YXImTjH_8w>yn2@r%kznCAv zhhp0`2mpxVj5j%o&5i)?`r7iES|8dA@p2kk@+XS(tjBGN)6>tm^=gayC<Y`($r_<` zRJckL&Y<Mc=F_Zo^XU-8$DCv}`Zc~!4oqJC=rPF_!*&E>n`gTEC*K74Y~{<Z=a`j_ z8Pwx!`j8jE$dKDL&A6fI18315!F*dRIqtBPpR&1~2pI{racagT&hbW`Yk!K=g69vw zoQuc^GnR6Jg(*0msuKhvK(fB?gEq4Ox@p7N`i0twY%_v02t(2^L@FeKvt>I_PREk) z)PstIMx1RxB@cK8%Mey%;nVnKriAKUk2Ky?dBMG3uXItKL$3N(#3P^pQa*K$l)wUy F^>pMLK0g2e literal 0 HcmV?d00001 diff --git a/pkg/provenance/testdata/helm-test-key.secret b/pkg/provenance/testdata/helm-test-key.secret new file mode 100644 index 0000000000000000000000000000000000000000..a966aef93ed97d01d764f29940738df6df2d9d24 GIT binary patch literal 2545 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 H0+01{(=@zm literal 0 HcmV?d00001 diff --git a/pkg/provenance/testdata/msgblock.yaml b/pkg/provenance/testdata/msgblock.yaml new file mode 100644 index 000000000..0fdbda8ce --- /dev/null +++ b/pkg/provenance/testdata/msgblock.yaml @@ -0,0 +1,7 @@ +description: Test chart versioning +name: hashtest +version: 1.2.3 + +... +files: + hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 diff --git a/pkg/provenance/testdata/msgblock.yaml.asc b/pkg/provenance/testdata/msgblock.yaml.asc new file mode 100644 index 000000000..5a34d6c52 --- /dev/null +++ b/pkg/provenance/testdata/msgblock.yaml.asc @@ -0,0 +1,21 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +description: Test chart versioning +name: hashtest +version: 1.2.3 + +... +files: + hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 +-----BEGIN PGP SIGNATURE----- +Comment: GPGTools - https://gpgtools.org + +iQEcBAEBCgAGBQJXlp8KAAoJEIQ7v5gfwYdiE7sIAJYDiza+asekeooSXLvQiK+G +PKnveqQpx49EZ6L7Y7UlW25SyH8EjXXHeJysDywCXF3w4luxN9n56ffU0KEW11IY +F+JSjmgIWLS6ti7ZAGEi6JInQ/30rOAIpTEBRBL2IueW3m63mezrGK6XkBlGqpor +C9WKeqLi+DWlMoBtsEy3Uk0XP6pn/qBFICYAbLQQU0sCCUT8CBA8f8aidxi7aw9t +i404yYF+Dvc6i4JlSG77SV0ZJBWllUvsWoCd9Jli0NAuaMqmE7mzcEt/dE+Fm2Ql +Bx3tr1WS4xTRiFQdcOttOl93H+OaHTh+Y0qqLTzzpCvqmttG0HfI6lMeCs7LeyA= +=vEK+ +-----END PGP SIGNATURE----- diff --git a/pkg/provenance/testdata/msgblock.yaml.tampered b/pkg/provenance/testdata/msgblock.yaml.tampered new file mode 100644 index 000000000..f15811bb2 --- /dev/null +++ b/pkg/provenance/testdata/msgblock.yaml.tampered @@ -0,0 +1,21 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +description: Test chart versioning +name: hashtest +version: 1.2.3+tampered + +... +files: + hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 +-----BEGIN PGP SIGNATURE----- +Comment: GPGTools - https://gpgtools.org + +iQEcBAEBCgAGBQJXlp8KAAoJEIQ7v5gfwYdiE7sIAJYDiza+asekeooSXLvQiK+G +PKnveqQpx49EZ6L7Y7UlW25SyH8EjXXHeJysDywCXF3w4luxN9n56ffU0KEW11IY +F+JSjmgIWLS6ti7ZAGEi6JInQ/30rOAIpTEBRBL2IueW3m63mezrGK6XkBlGqpor +C9WKeqLi+DWlMoBtsEy3Uk0XP6pn/qBFICYAbLQQU0sCCUT8CBA8f8aidxi7aw9t +i404yYF+Dvc6i4JlSG77SV0ZJBWllUvsWoCd9Jli0NAuaMqmE7mzcEt/dE+Fm2Ql +Bx3tr1WS4xTRiFQdcOttOl93H+OaHTh+Y0qqLTzzpCvqmttG0HfI6lMeCs7LeyA= +=vEK+ +-----END PGP SIGNATURE----- diff --git a/pkg/provenance/testdata/regen-hashtest.sh b/pkg/provenance/testdata/regen-hashtest.sh new file mode 100755 index 000000000..4381fd0b1 --- /dev/null +++ b/pkg/provenance/testdata/regen-hashtest.sh @@ -0,0 +1,3 @@ +#!/bin/sh +helm package hashtest +shasum -a 256 hashtest-1.2.3.tgz > hashtest.sha256 -- GitLab