Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cli/slsa-verifier/verify/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
package verify

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"io"
"os"
"strings"
)

func computeFileHash(filePath string, h hash.Hash) (string, error) {
Expand All @@ -33,3 +36,38 @@ func computeFileHash(filePath string, h hash.Hash) (string, error) {
}
return hex.EncodeToString(h.Sum(nil)), nil
}

// computeArtifactHash returns the hash of the artifact.
// If artifact is a digest (sha256:xxx or sha512:xxx), it parses and validates it.
// Otherwise, it computes the SHA256 hash of the file at the given path.
func computeArtifactHash(artifact string) (string, error) {
if strings.HasPrefix(artifact, "sha256:") || strings.HasPrefix(artifact, "sha512:") {
return parseDigest(artifact)
}
return computeFileHash(artifact, sha256.New())
}

// parseDigest parses and validates a digest string in the format "algorithm:hexvalue"
// and returns the hex value.
func parseDigest(digest string) (string, error) {
parts := strings.SplitN(digest, ":", 2)
if len(parts) != 2 {
return "", fmt.Errorf("invalid digest format: %s", digest)
}
algorithm := parts[0]
h := parts[1]

expectedLen := map[string]int{"sha256": 64, "sha512": 128}
exp, ok := expectedLen[algorithm]
if !ok {
return "", fmt.Errorf("unsupported digest algorithm: %s (supported: sha256, sha512)", algorithm)
}
if len(h) != exp {
return "", fmt.Errorf("invalid %s digest length: expected %d characters, got %d", algorithm, exp, len(h))
}
if _, err := hex.DecodeString(h); err != nil {
return "", fmt.Errorf("invalid hex in digest: %s", h)
}

return h, nil
}
173 changes: 173 additions & 0 deletions cli/slsa-verifier/verify/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2023 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
"os"
"path/filepath"
"strings"
"testing"
)

func TestParseDigest(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "valid sha256",
input: "sha256:" + strings.Repeat("a", 64),
want: strings.Repeat("a", 64),
wantErr: false,
},
{
name: "valid sha512",
input: "sha512:" + strings.Repeat("b", 128),
want: strings.Repeat("b", 128),
wantErr: false,
},
{
name: "valid sha256 with mixed case hex",
input: "sha256:" + strings.Repeat("aB", 32),
want: strings.Repeat("aB", 32),
wantErr: false,
},
{
name: "sha256 wrong length - too short",
input: "sha256:abc",
want: "",
wantErr: true,
},
{
name: "sha256 wrong length - too long",
input: "sha256:" + strings.Repeat("a", 65),
want: "",
wantErr: true,
},
{
name: "sha512 wrong length",
input: "sha512:" + strings.Repeat("a", 64),
want: "",
wantErr: true,
},
{
name: "invalid hex characters",
input: "sha256:" + strings.Repeat("g", 64),
want: "",
wantErr: true,
},
{
name: "unsupported algorithm sha384",
input: "sha384:" + strings.Repeat("a", 96),
want: "",
wantErr: true,
},
{
name: "unsupported algorithm md5",
input: "md5:" + strings.Repeat("a", 32),
want: "",
wantErr: true,
},
{
name: "missing colon",
input: "sha256" + strings.Repeat("a", 64),
want: "",
wantErr: true,
},
{
name: "empty hash value",
input: "sha256:",
want: "",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseDigest(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("parseDigest() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("parseDigest() = %v, want %v", got, tt.want)
}
})
}
}

func TestComputeArtifactHash(t *testing.T) {
// Create a temporary file for testing file hash computation
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "test.txt")
content := []byte("hello world")
if err := os.WriteFile(tmpFile, content, 0o600); err != nil {
t.Fatalf("failed to create temp file: %v", err)
}
// SHA256 of "hello world" is b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "sha256 digest",
input: "sha256:" + strings.Repeat("a", 64),
want: strings.Repeat("a", 64),
wantErr: false,
},
{
name: "sha512 digest",
input: "sha512:" + strings.Repeat("b", 128),
want: strings.Repeat("b", 128),
wantErr: false,
},
{
name: "file path",
input: tmpFile,
want: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
wantErr: false,
},
{
name: "invalid digest format",
input: "sha256:invalid",
want: "",
wantErr: true,
},
{
name: "non-existent file",
input: "/non/existent/file.txt",
want: "",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := computeArtifactHash(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("computeArtifactHash() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("computeArtifactHash() = %v, want %v", got, tt.want)
}
})
}
}
3 changes: 1 addition & 2 deletions cli/slsa-verifier/verify/verify_artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package verify

import (
"context"
"crypto/sha256"
"fmt"
"os"

Expand All @@ -41,7 +40,7 @@ func (c *VerifyArtifactCommand) Exec(ctx context.Context, artifacts []string) (*
var builderID *utils.TrustedBuilderID

for _, artifact := range artifacts {
artifactHash, err := computeFileHash(artifact, sha256.New())
artifactHash, err := computeArtifactHash(artifact)
if err != nil {
fmt.Fprintf(os.Stderr, "Verifying artifact %s: FAILED: %v\n\n", artifact, err)
return nil, err
Expand Down
Loading