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 }