github.com/sri09kanth/helm@v3.0.0-beta.3+incompatible/pkg/provenance/sign_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package provenance
    17  
    18  import (
    19  	"io/ioutil"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	pgperrors "golang.org/x/crypto/openpgp/errors"
    26  )
    27  
    28  const (
    29  	// testKeyFile is the secret key.
    30  	// Generating keys should be done with `gpg --gen-key`. The current key
    31  	// was generated to match Go's defaults (RSA/RSA 2048). It has no pass
    32  	// phrase. Use `gpg --export-secret-keys helm-test` to export the secret.
    33  	testKeyfile = "testdata/helm-test-key.secret"
    34  
    35  	// testPasswordKeyFile is a keyfile with a password.
    36  	testPasswordKeyfile = "testdata/helm-password-key.secret"
    37  
    38  	// testPubfile is the public key file.
    39  	// Use `gpg --export helm-test` to export the public key.
    40  	testPubfile = "testdata/helm-test-key.pub"
    41  
    42  	// Generated name for the PGP key in testKeyFile.
    43  	testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>`
    44  
    45  	testPasswordKeyName = `password key (fake) <fake@helm.sh>`
    46  
    47  	testChartfile = "testdata/hashtest-1.2.3.tgz"
    48  
    49  	// testSigBlock points to a signature generated by an external tool.
    50  	// This file was generated with GnuPG:
    51  	// gpg --clearsign -u helm-test --openpgp testdata/msgblock.yaml
    52  	testSigBlock = "testdata/msgblock.yaml.asc"
    53  
    54  	// testTamperedSigBlock is a tampered copy of msgblock.yaml.asc
    55  	testTamperedSigBlock = "testdata/msgblock.yaml.tampered"
    56  
    57  	// testSumfile points to a SHA256 sum generated by an external tool.
    58  	// We always want to validate against an external tool's representation to
    59  	// verify that we haven't done something stupid. This file was generated
    60  	// with shasum.
    61  	// shasum -a 256 hashtest-1.2.3.tgz > testdata/hashtest.sha256
    62  	testSumfile = "testdata/hashtest.sha256"
    63  )
    64  
    65  // testMessageBlock represents the expected message block for the testdata/hashtest chart.
    66  const testMessageBlock = `apiVersion: v1
    67  description: Test chart versioning
    68  name: hashtest
    69  version: 1.2.3
    70  
    71  ...
    72  files:
    73    hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888
    74  `
    75  
    76  func TestMessageBlock(t *testing.T) {
    77  	out, err := messageBlock(testChartfile)
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	got := out.String()
    82  
    83  	if got != testMessageBlock {
    84  		t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got)
    85  	}
    86  }
    87  
    88  func TestParseMessageBlock(t *testing.T) {
    89  	md, sc, err := parseMessageBlock([]byte(testMessageBlock))
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	if md.Name != "hashtest" {
    95  		t.Errorf("Expected name %q, got %q", "hashtest", md.Name)
    96  	}
    97  
    98  	if lsc := len(sc.Files); lsc != 1 {
    99  		t.Errorf("Expected 1 file, got %d", lsc)
   100  	}
   101  
   102  	if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok {
   103  		t.Errorf("hashtest file not found in Files")
   104  	} else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" {
   105  		t.Errorf("Unexpected hash: %q", hash)
   106  	}
   107  }
   108  
   109  func TestLoadKey(t *testing.T) {
   110  	k, err := loadKey(testKeyfile)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  
   115  	if _, ok := k.Identities[testKeyName]; !ok {
   116  		t.Errorf("Expected to load a key for user %q", testKeyName)
   117  	}
   118  }
   119  
   120  func TestLoadKeyRing(t *testing.T) {
   121  	k, err := loadKeyRing(testPubfile)
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  
   126  	if len(k) > 1 {
   127  		t.Errorf("Expected 1, got %d", len(k))
   128  	}
   129  
   130  	for _, e := range k {
   131  		if ii, ok := e.Identities[testKeyName]; !ok {
   132  			t.Errorf("Expected %s in %v", testKeyName, ii)
   133  		}
   134  	}
   135  }
   136  
   137  func TestDigest(t *testing.T) {
   138  	f, err := os.Open(testChartfile)
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	defer f.Close()
   143  
   144  	hash, err := Digest(f)
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  
   149  	sig, err := readSumFile(testSumfile)
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	if !strings.Contains(sig, hash) {
   155  		t.Errorf("Expected %s to be in %s", hash, sig)
   156  	}
   157  }
   158  
   159  func TestNewFromFiles(t *testing.T) {
   160  	s, err := NewFromFiles(testKeyfile, testPubfile)
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	if _, ok := s.Entity.Identities[testKeyName]; !ok {
   166  		t.Errorf("Expected to load a key for user %q", testKeyName)
   167  	}
   168  }
   169  
   170  func TestDigestFile(t *testing.T) {
   171  	hash, err := DigestFile(testChartfile)
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  
   176  	sig, err := readSumFile(testSumfile)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	if !strings.Contains(sig, hash) {
   182  		t.Errorf("Expected %s to be in %s", hash, sig)
   183  	}
   184  }
   185  
   186  func TestDecryptKey(t *testing.T) {
   187  	k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  
   192  	if !k.Entity.PrivateKey.Encrypted {
   193  		t.Fatal("Key is not encrypted")
   194  	}
   195  
   196  	// We give this a simple callback that returns the password.
   197  	if err := k.DecryptKey(func(s string) ([]byte, error) {
   198  		return []byte("secret"), nil
   199  	}); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  
   203  	// Re-read the key (since we already unlocked it)
   204  	k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	// Now we give it a bogus password.
   209  	if err := k.DecryptKey(func(s string) ([]byte, error) {
   210  		return []byte("secrets_and_lies"), nil
   211  	}); err == nil {
   212  		t.Fatal("Expected an error when giving a bogus passphrase")
   213  	}
   214  }
   215  
   216  func TestClearSign(t *testing.T) {
   217  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  
   222  	sig, err := signer.ClearSign(testChartfile)
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	t.Logf("Sig:\n%s", sig)
   227  
   228  	if !strings.Contains(sig, testMessageBlock) {
   229  		t.Errorf("expected message block to be in sig: %s", sig)
   230  	}
   231  }
   232  
   233  func TestDecodeSignature(t *testing.T) {
   234  	// Unlike other tests, this does a round-trip test, ensuring that a signature
   235  	// generated by the library can also be verified by the library.
   236  
   237  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	sig, err := signer.ClearSign(testChartfile)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  
   247  	f, err := ioutil.TempFile("", "helm-test-sig-")
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  
   252  	tname := f.Name()
   253  	defer func() {
   254  		os.Remove(tname)
   255  	}()
   256  	f.WriteString(sig)
   257  	f.Close()
   258  
   259  	sig2, err := signer.decodeSignature(tname)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	by, err := signer.verifySignature(sig2)
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  
   269  	if _, ok := by.Identities[testKeyName]; !ok {
   270  		t.Errorf("Expected identity %q", testKeyName)
   271  	}
   272  }
   273  
   274  func TestVerify(t *testing.T) {
   275  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  
   280  	if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil {
   281  		t.Errorf("Failed to pass verify. Err: %s", err)
   282  	} else if len(ver.FileHash) == 0 {
   283  		t.Error("Verification is missing hash.")
   284  	} else if ver.SignedBy == nil {
   285  		t.Error("No SignedBy field")
   286  	} else if ver.FileName != filepath.Base(testChartfile) {
   287  		t.Errorf("FileName is unexpectedly %q", ver.FileName)
   288  	}
   289  
   290  	if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
   291  		t.Errorf("Expected %s to fail.", testTamperedSigBlock)
   292  	}
   293  
   294  	switch err.(type) {
   295  	case pgperrors.SignatureError:
   296  		t.Logf("Tampered sig block error: %s (%T)", err, err)
   297  	default:
   298  		t.Errorf("Expected invalid signature error, got %q (%T)", err, err)
   299  	}
   300  }
   301  
   302  // readSumFile reads a file containing a sum generated by the UNIX shasum tool.
   303  func readSumFile(sumfile string) (string, error) {
   304  	data, err := ioutil.ReadFile(sumfile)
   305  	if err != nil {
   306  		return "", err
   307  	}
   308  
   309  	sig := string(data)
   310  	parts := strings.SplitN(sig, " ", 2)
   311  	return parts[0], nil
   312  }