github.com/koderover/helm@v2.17.0+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 = `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 }