mirror of https://github.com/helm/helm
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 #983pull/988/head
parent
64b73081ee
commit
ce83a8a777
@ -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"
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 hashtest-1.2.3.tgz
|
@ -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
|
@ -0,0 +1,3 @@
|
|||||||
|
description: Test chart versioning
|
||||||
|
name: hashtest
|
||||||
|
version: 1.2.3
|
@ -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
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,7 @@
|
|||||||
|
description: Test chart versioning
|
||||||
|
name: hashtest
|
||||||
|
version: 1.2.3
|
||||||
|
|
||||||
|
...
|
||||||
|
files:
|
||||||
|
hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75
|
@ -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-----
|
@ -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-----
|
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
helm package hashtest
|
||||||
|
shasum -a 256 hashtest-1.2.3.tgz > hashtest.sha256
|
Loading…
Reference in new issue