github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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  	"strings"
    22  	"testing"
    23  
    24  	pgperrors "golang.org/x/crypto/openpgp/errors"
    25  )
    26  
    27  const (
    28  	// testKeyFile is the secret key.
    29  	// Generating keys should be done with `gpg --gen-key`. The current key
    30  	// was generated to match Go's defaults (RSA/RSA 2048). It has no pass
    31  	// phrase. Use `gpg --export-secret-keys helm-test` to export the secret.
    32  	testKeyfile = "testdata/helm-test-key.secret"
    33  
    34  	// testPubfile is the public key file.
    35  	// Use `gpg --export helm-test` to export the public key.
    36  	testPubfile = "testdata/helm-test-key.pub"
    37  
    38  	// Generated name for the PGP key in testKeyFile.
    39  	testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>`
    40  
    41  	testChartfile = "testdata/hashtest-1.2.3.tgz"
    42  
    43  	// testSigBlock points to a signature generated by an external tool.
    44  	// This file was generated with GnuPG:
    45  	// gpg --clearsign -u helm-test --openpgp testdata/msgblock.yaml
    46  	testSigBlock = "testdata/msgblock.yaml.asc"
    47  
    48  	// testTamperedSigBlock is a tampered copy of msgblock.yaml.asc
    49  	testTamperedSigBlock = "testdata/msgblock.yaml.tampered"
    50  
    51  	// testSumfile points to a SHA256 sum generated by an external tool.
    52  	// We always want to validate against an external tool's representation to
    53  	// verify that we haven't done something stupid. This file was generated
    54  	// with shasum.
    55  	// shasum -a 256 hashtest-1.2.3.tgz > testdata/hashtest.sha256
    56  	testSumfile = "testdata/hashtest.sha256"
    57  )
    58  
    59  // testMessageBlock represents the expected message block for the testdata/hashtest chart.
    60  const testMessageBlock = `description: Test chart versioning
    61  name: hashtest
    62  version: 1.2.3
    63  
    64  ...
    65  files:
    66    hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75
    67  `
    68  
    69  func TestMessageBlock(t *testing.T) {
    70  	out, err := messageBlock(testChartfile)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	got := out.String()
    75  
    76  	if got != testMessageBlock {
    77  		t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got)
    78  	}
    79  }
    80  
    81  func TestParseMessageBlock(t *testing.T) {
    82  	md, sc, err := parseMessageBlock([]byte(testMessageBlock))
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  
    87  	if md.Name != "hashtest" {
    88  		t.Errorf("Expected name %q, got %q", "hashtest", md.Name)
    89  	}
    90  
    91  	if lsc := len(sc.Files); lsc != 1 {
    92  		t.Errorf("Expected 1 file, got %d", lsc)
    93  	}
    94  
    95  	if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok {
    96  		t.Errorf("hashtest file not found in Files")
    97  	} else if hash != "sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75" {
    98  		t.Errorf("Unexpected hash: %q", hash)
    99  	}
   100  }
   101  
   102  func TestLoadKey(t *testing.T) {
   103  	k, err := loadKey(testKeyfile)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	if _, ok := k.Identities[testKeyName]; !ok {
   109  		t.Errorf("Expected to load a key for user %q", testKeyName)
   110  	}
   111  }
   112  
   113  func TestLoadKeyRing(t *testing.T) {
   114  	k, err := loadKeyRing(testPubfile)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  
   119  	if len(k) > 1 {
   120  		t.Errorf("Expected 1, got %d", len(k))
   121  	}
   122  
   123  	for _, e := range k {
   124  		if ii, ok := e.Identities[testKeyName]; !ok {
   125  			t.Errorf("Expected %s in %v", testKeyName, ii)
   126  		}
   127  	}
   128  }
   129  
   130  func TestNewFromFiles(t *testing.T) {
   131  	s, err := NewFromFiles(testKeyfile, testPubfile)
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	if _, ok := s.Entity.Identities[testKeyName]; !ok {
   137  		t.Errorf("Expected to load a key for user %q", testKeyName)
   138  	}
   139  }
   140  
   141  func TestSumArchive(t *testing.T) {
   142  	hash, err := sumArchive(testChartfile)
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	sig, err := readSumFile(testSumfile)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	if !strings.Contains(sig, hash) {
   153  		t.Errorf("Expected %s to be in %s", hash, sig)
   154  	}
   155  }
   156  
   157  func TestClearSign(t *testing.T) {
   158  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  
   163  	sig, err := signer.ClearSign(testChartfile)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	t.Logf("Sig:\n%s", sig)
   168  
   169  	if !strings.Contains(sig, testMessageBlock) {
   170  		t.Errorf("expected message block to be in sig: %s", sig)
   171  	}
   172  }
   173  
   174  func TestDecodeSignature(t *testing.T) {
   175  	// Unlike other tests, this does a round-trip test, ensuring that a signature
   176  	// generated by the library can also be verified by the library.
   177  
   178  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	sig, err := signer.ClearSign(testChartfile)
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  
   188  	f, err := ioutil.TempFile("", "helm-test-sig-")
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	tname := f.Name()
   194  	defer func() {
   195  		os.Remove(tname)
   196  	}()
   197  	f.WriteString(sig)
   198  	f.Close()
   199  
   200  	sig2, err := signer.decodeSignature(tname)
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  
   205  	by, err := signer.verifySignature(sig2)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  
   210  	if _, ok := by.Identities[testKeyName]; !ok {
   211  		t.Errorf("Expected identity %q", testKeyName)
   212  	}
   213  }
   214  
   215  func TestVerify(t *testing.T) {
   216  	signer, err := NewFromFiles(testKeyfile, testPubfile)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil {
   222  		t.Errorf("Failed to pass verify. Err: %s", err)
   223  	} else if len(ver.FileHash) == 0 {
   224  		t.Error("Verification is missing hash.")
   225  	} else if ver.SignedBy == nil {
   226  		t.Error("No SignedBy field")
   227  	}
   228  
   229  	if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
   230  		t.Errorf("Expected %s to fail.", testTamperedSigBlock)
   231  	}
   232  
   233  	switch err.(type) {
   234  	case pgperrors.SignatureError:
   235  		t.Logf("Tampered sig block error: %s (%T)", err, err)
   236  	default:
   237  		t.Errorf("Expected invalid signature error, got %q (%T)", err, err)
   238  	}
   239  }
   240  
   241  // readSumFile reads a file containing a sum generated by the UNIX shasum tool.
   242  func readSumFile(sumfile string) (string, error) {
   243  	data, err := ioutil.ReadFile(sumfile)
   244  	if err != nil {
   245  		return "", err
   246  	}
   247  
   248  	sig := string(data)
   249  	parts := strings.SplitN(sig, " ", 2)
   250  	return parts[0], nil
   251  }