github.com/lrills/helm@v2.8.1+incompatible/pkg/provenance/sign_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     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 = `description: Test chart versioning
    67  name: hashtest
    68  version: 1.2.3
    69  
    70  ...
    71  files:
    72    hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75
    73  `
    74  
    75  func TestMessageBlock(t *testing.T) {
    76  	out, err := messageBlock(testChartfile)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	got := out.String()
    81  
    82  	if got != testMessageBlock {
    83  		t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got)
    84  	}
    85  }
    86  
    87  func TestParseMessageBlock(t *testing.T) {
    88  	md, sc, err := parseMessageBlock([]byte(testMessageBlock))
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	if md.Name != "hashtest" {
    94  		t.Errorf("Expected name %q, got %q", "hashtest", md.Name)
    95  	}
    96  
    97  	if lsc := len(sc.Files); lsc != 1 {
    98  		t.Errorf("Expected 1 file, got %d", lsc)
    99  	}
   100  
   101  	if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok {
   102  		t.Errorf("hashtest file not found in Files")
   103  	} else if hash != "sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75" {
   104  		t.Errorf("Unexpected hash: %q", hash)
   105  	}
   106  }
   107  
   108  func TestLoadKey(t *testing.T) {
   109  	k, err := loadKey(testKeyfile)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	if _, ok := k.Identities[testKeyName]; !ok {
   115  		t.Errorf("Expected to load a key for user %q", testKeyName)
   116  	}
   117  }
   118  
   119  func TestLoadKeyRing(t *testing.T) {
   120  	k, err := loadKeyRing(testPubfile)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	if len(k) > 1 {
   126  		t.Errorf("Expected 1, got %d", len(k))
   127  	}
   128  
   129  	for _, e := range k {
   130  		if ii, ok := e.Identities[testKeyName]; !ok {
   131  			t.Errorf("Expected %s in %v", testKeyName, ii)
   132  		}
   133  	}
   134  }
   135  
   136  func TestDigest(t *testing.T) {
   137  	f, err := os.Open(testChartfile)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  	defer f.Close()
   142  
   143  	hash, err := Digest(f)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	sig, err := readSumFile(testSumfile)
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  
   153  	if !strings.Contains(sig, hash) {
   154  		t.Errorf("Expected %s to be in %s", hash, sig)
   155  	}
   156  }
   157  
   158  func TestNewFromFiles(t *testing.T) {
   159  	s, err := NewFromFiles(testKeyfile, testPubfile)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  
   164  	if _, ok := s.Entity.Identities[testKeyName]; !ok {
   165  		t.Errorf("Expected to load a key for user %q", testKeyName)
   166  	}
   167  }
   168  
   169  func TestDigestFile(t *testing.T) {
   170  	hash, err := DigestFile(testChartfile)
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  
   175  	sig, err := readSumFile(testSumfile)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	if !strings.Contains(sig, hash) {
   181  		t.Errorf("Expected %s to be in %s", hash, sig)
   182  	}
   183  }
   184  
   185  func TestDecryptKey(t *testing.T) {
   186  	k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	if !k.Entity.PrivateKey.Encrypted {
   192  		t.Fatal("Key is not encrypted")
   193  	}
   194  
   195  	// We give this a simple callback that returns the password.
   196  	if err := k.DecryptKey(func(s string) ([]byte, error) {
   197  		return []byte("secret"), nil
   198  	}); err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	// Re-read the key (since we already unlocked it)
   203  	k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	// Now we give it a bogus password.
   208  	if err := k.DecryptKey(func(s string) ([]byte, error) {
   209  		return []byte("secrets_and_lies"), nil
   210  	}); err == nil {
   211  		t.Fatal("Expected an error when giving a bogus passphrase")
   212  	}
   213  }
   214  
   215  func TestClearSign(t *testing.T) {
   216  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	sig, err := signer.ClearSign(testChartfile)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	t.Logf("Sig:\n%s", sig)
   226  
   227  	if !strings.Contains(sig, testMessageBlock) {
   228  		t.Errorf("expected message block to be in sig: %s", sig)
   229  	}
   230  }
   231  
   232  func TestDecodeSignature(t *testing.T) {
   233  	// Unlike other tests, this does a round-trip test, ensuring that a signature
   234  	// generated by the library can also be verified by the library.
   235  
   236  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  
   241  	sig, err := signer.ClearSign(testChartfile)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  
   246  	f, err := ioutil.TempFile("", "helm-test-sig-")
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  
   251  	tname := f.Name()
   252  	defer func() {
   253  		os.Remove(tname)
   254  	}()
   255  	f.WriteString(sig)
   256  	f.Close()
   257  
   258  	sig2, err := signer.decodeSignature(tname)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	by, err := signer.verifySignature(sig2)
   264  	if err != nil {
   265  		t.Fatal(err)
   266  	}
   267  
   268  	if _, ok := by.Identities[testKeyName]; !ok {
   269  		t.Errorf("Expected identity %q", testKeyName)
   270  	}
   271  }
   272  
   273  func TestVerify(t *testing.T) {
   274  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  
   279  	if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil {
   280  		t.Errorf("Failed to pass verify. Err: %s", err)
   281  	} else if len(ver.FileHash) == 0 {
   282  		t.Error("Verification is missing hash.")
   283  	} else if ver.SignedBy == nil {
   284  		t.Error("No SignedBy field")
   285  	} else if ver.FileName != filepath.Base(testChartfile) {
   286  		t.Errorf("FileName is unexpectedly %q", ver.FileName)
   287  	}
   288  
   289  	if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
   290  		t.Errorf("Expected %s to fail.", testTamperedSigBlock)
   291  	}
   292  
   293  	switch err.(type) {
   294  	case pgperrors.SignatureError:
   295  		t.Logf("Tampered sig block error: %s (%T)", err, err)
   296  	default:
   297  		t.Errorf("Expected invalid signature error, got %q (%T)", err, err)
   298  	}
   299  }
   300  
   301  // readSumFile reads a file containing a sum generated by the UNIX shasum tool.
   302  func readSumFile(sumfile string) (string, error) {
   303  	data, err := ioutil.ReadFile(sumfile)
   304  	if err != nil {
   305  		return "", err
   306  	}
   307  
   308  	sig := string(data)
   309  	parts := strings.SplitN(sig, " ", 2)
   310  	return parts[0], nil
   311  }