diff --git a/engine/shasum/shasum.go b/engine/shasum/shasum.go new file mode 100644 index 00000000..3c6d504d --- /dev/null +++ b/engine/shasum/shasum.go @@ -0,0 +1,72 @@ +package shasum + +import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "fmt" + "io" + "strings" +) + +// Check is a calculates and verifies a file checksum. +// This supports the sha1, sha256 and sha512 values. +func Check(in, checksum string) bool { + hash, size, _ := split(checksum) + + // if a byte size is provided for the + // Yaml file it must match. + if size > 0 && int64(len(in)) != size { + return false + } + + switch len(hash) { + case 64: + return sha256sum(in) == hash + case 128: + return sha512sum(in) == hash + case 40: + return sha1sum(in) == hash + case 0: + return true // if no checksum assume valid + } + + return false +} + +func sha1sum(in string) string { + h := sha1.New() + io.WriteString(h, in) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func sha256sum(in string) string { + h := sha256.New() + io.WriteString(h, in) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func sha512sum(in string) string { + h := sha512.New() + io.WriteString(h, in) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func split(in string) (string, int64, string) { + var hash string + var name string + var size int64 + + // the checksum might be split into multiple + // sections including the file size and name. + switch strings.Count(in, " ") { + case 1: + fmt.Sscanf(in, "%s %s", &hash, &name) + case 2: + fmt.Sscanf(in, "%s %d %s", &hash, &size, &name) + default: + hash = in + } + + return hash, size, name +} diff --git a/engine/shasum/shasum_test.go b/engine/shasum/shasum_test.go new file mode 100644 index 00000000..ff7c4ccc --- /dev/null +++ b/engine/shasum/shasum_test.go @@ -0,0 +1,97 @@ +package shasum + +import ( + "testing" + + "github.com/franela/goblin" +) + +func TestParse(t *testing.T) { + + g := goblin.Goblin(t) + g.Describe("Shasum", func() { + + g.It("Should parse the shasum string", func() { + hash, _, _ := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + }) + + g.It("Should parse a two-part shasum string", func() { + hash, _, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 .drone.yml") + g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + g.Assert(name).Equal(".drone.yml") + }) + + g.It("Should parse a three-part shasum string", func() { + hash, size, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 42 .drone.yml") + g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + g.Assert(name).Equal(".drone.yml") + g.Assert(size).Equal(int64(42)) + }) + + g.It("Should calc a sha1 sum", func() { + hash := sha1sum("foo\n") + g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + }) + + g.It("Should calc a sha256 sum", func() { + hash := sha256sum("foo\n") + g.Assert(hash).Equal("b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c") + }) + + g.It("Should calc a sha512 sum", func() { + hash := sha512sum("foo\n") + g.Assert(hash).Equal("0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6") + }) + + g.It("Should calc a sha1 sum", func() { + hash := sha1sum("foo\n") + g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15") + }) + + g.It("Should validate sha1 sum with file size", func() { + ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -") + g.Assert(ok).IsTrue() + }) + + g.It("Should validate sha256 sum with file size", func() { + ok := Check("foo\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -") + g.Assert(ok).IsTrue() + }) + + g.It("Should validate sha512 sum with file size", func() { + ok := Check("foo\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -") + g.Assert(ok).IsTrue() + }) + + g.It("Should fail validation if incorrect sha1", func() { + ok := Check("bar\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -") + g.Assert(ok).IsFalse() + }) + + g.It("Should fail validation if incorrect sha256", func() { + ok := Check("bar\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -") + g.Assert(ok).IsFalse() + }) + + g.It("Should fail validation if incorrect sha512", func() { + ok := Check("bar\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -") + g.Assert(ok).IsFalse() + }) + + g.It("Should return false if file size mismatch", func() { + ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 12 -") + g.Assert(ok).IsFalse() + }) + + g.It("Should return false if invalid checksum string", func() { + ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15234") + g.Assert(ok).IsFalse() + }) + + g.It("Should return true if empty checksum", func() { + ok := Check("foo\n", "") + g.Assert(ok).IsTrue() + }) + }) +}